11

I'm trying to build a long command involving find. I have an array of directories that I want to ignore, and I want to format this directory into the command.

Basically, I want to transform this array:

declare -a ignore=(archive crl cfg)

into this:

-o -path "$dir/archive" -prune -o -path "$dir/crl" -prune -o -path "$dir/cfg" -prune

This way, I can simply add directories to the array, and the find command will adjust accordingly.

So far, I figured out how to prepend or append using

${ignore[@]/#/-o -path \"\$dir/}
${ignore[@]/%/\" -prune}

But I don't know how to combine these and simultaneously prepend and append to each element of an array.

1
  • Sounds like a good idea. I think you'll need to have multiple levels (copies) of the array to support each ${var/x/y} substitution. I have done similar using something like $( echo $(echo ${ig[@]} | sed 's/a/b/g;s/c/d/g;s/d/e$/;s/^f/g/') ) . Good luck! Commented Jul 11, 2013 at 20:37

4 Answers 4

17

You cannot do it simultaneously easily. Fortunately, you do not need to:

ignore=( archive crl cfg                    )
ignore=( "${ignore[@]/%/\" -prune}"         )
ignore=( "${ignore[@]/#/-o -path \"\$dir/}" )

echo ${ignore[@]}

Note the parentheses and double quotes - they make sure the array contains three elements after each substitution, even if there are spaces involved.

Sign up to request clarification or add additional context in comments.

3 Comments

better yet! Good luck to all.
It's good that you tried to quote the names, but it's not perfect, e.g. if ignore contains some weird things like ".
"You cannot do it simultaneously" is wrong, see stackoverflow.com/a/35550176/1862762
3

In general, you should strive to always treat each variable in the quoted form (e.g. "${ignore[@]}") instead of trying to insert quotation marks yourself (just as you should use parameterized statements instead of escaping the input in SQL) because it's hard to be perfect by manual escaping; for example, suppose a variable contains a quotation mark.

In this regard, I would aim at crafting an array where each argument word for find becomes an element: ("-o" "-path" "$dir/archive" "-prune" "-o" "-path" "$dir/crl" "-prune" "-o" "-path" "$dir/cfg" "-prune") (a 12-element array).

Unfortunately, Bash doesn't seem to support a form of parameter expansion where each element expands to multiple words. (p{1,2,3}q expands to p1q p2q p3q, but with a=(1 2 3), p"${a[@]}"q expands to p1 2 3q.) So you need to resort to a loop:

declare -a args=()
for i in "${ignore[@]}"
do
    args+=(-o -path "$dir/$i" -prune) # I'm not sure if you want to have
                                      # $dir expanded at this point;
                                      # otherwise, just use "\$dir/$i".
done

find ... "${args[@]}" ...

Comments

3

Have a look at printf, which does the job as well:

printf -- '-o -path "$dir/%s" -prune ' ${ignore[@]}

Comments

2

If I understand right,

declare -a ignore=(archive crl cfg)
a=$(echo ${ignore[@]} | xargs -n1 -I% echo -o -path '"$dir/%"' -prune)
echo $a

prints

-o -path "$dir/archive" -prune -o -path "$dir/crl" -prune -o -path "$dir/cfg" -prune

Works only with xargs what has the next switches:

 -I replstr
         Execute utility for each input line, replacing one or more occurrences of replstr in up to replacements
         (or 5 if no -R flag is specified) arguments to utility with the entire line of input.  The resulting
         arguments, after replacement is done, will not be allowed to grow beyond 255 bytes; this is implemented
         by concatenating as much of the argument containing replstr as possible, to the constructed arguments to
         utility, up to 255 bytes.  The 255 byte limit does not apply to arguments to utility which do not contain
         replstr, and furthermore, no replacement will be done on utility itself.  Implies -x.

 -J replstr
         If this option is specified, xargs will use the data read from standard input to replace the first occur-
         rence of replstr instead of appending that data after all other arguments.  This option will not affect
         how many arguments will be read from input (-n), or the size of the command(s) xargs will generate (-s).
         The option just moves where those arguments will be placed in the command(s) that are executed.  The
         replstr must show up as a distinct argument to xargs.  It will not be recognized if, for instance, it is
         in the middle of a quoted string.  Furthermore, only the first occurrence of the replstr will be
         replaced.  For example, the following command will copy the list of files and directories which start
         with an uppercase letter in the current directory to destdir:

               /bin/ls -1d [A-Z]* | xargs -J % cp -rp % destdir

9 Comments

Whoa, this is really sexy too
This prints -o -path "$dir/archive crl cfg" -prune.
@musiphil please, copy exactly - and run it witg bash. It prints exactly what i stated in my answer. Double checked it right now.
@jm666: I really dragged over the whole section, copied it, and pasted it into my terminal. The same result again.
The manual for xargs (at least the GNU version) says that with -I, "unquoted blanks do not terminate input items; instead the separator is the newline character." And echo ${ignore[@]} prints the elements delimited by spaces, not newlines.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.