0

I'm trying to build a list of directories to exclude from a find command. Unfortunately sone of the directories have spaces in them. For example, suppose I have a list myList of three directories, the second of which is called "b c"

/bin/bash
myList="a \\
        b c \\
        d"

I'm unable to convince 'bash` not to break up the second line into two individual tokens.

for example, this code

dirList=
stringArray=($myList)
for i in "${stringArray[@]}" ; do
    dirList="$dirList -name $i -prune -o"
    echo "$dirList"
    echo " "
done

returns this:

-name a -prune -o

-name a -prune -o -name b -prune -o

-name a -prune -o -name b -prune -o -name c -prune -o

-name a -prune -o -name b -prune -o -name c -prune -o -name d -prune -o

But I want it to return

-name a -prune -o

-name a -prune -o -name b c -prune -o

-name a -prune -o -name b c -prune -o -name d -prune -o

The obvious way to deal with the space is to define the second line as "b c" But then I have nested double quotes. I've tried (I believe) all of the suggestions on the web about dealing with nested quotes, but none are working for m. Could somebody please advise how to do this?

muru
  • 72,889
Leo Simon
  • 453

3 Answers3

2

Why not simply define your list as an array from the very beginning, rather than as a string that you then parse into an array? That way you could create an array of strings, bounded by doublequotes, able to handle spaces in a non-tokenized fashion?

Frex:

#!/bin/bash
myList=("a" "b c" "d")
dirList=
stringArray=$myList
for i in "${stringArray[@]}" ; do
    dirList="$dirList -name $i -prune -o"
    echo "$dirList"
    echo " "
done
James S.
  • 183
1

You don't want the string -name a -prune -o name b c -prune ... either, since what actually goes to the command you run is not a single string, but multiple strings. It's just that when you write foo bar "a b", the shell interprets the quotes and splits the command line on unquoted whitespace, giving you the three strings foo, bar and a b. Similarly, what you want to eventually go to find is the arguments -o, -name, a b, -prune etc. If you have the string -o -name a b -prune, it's hard to make it so that the string is split on the other spaces, but isn't split between the a and the b.

First you need some way to read the newline-separated list of names, without breaking the individual lines. Since you mentioned Bash, there's the mapfile (readarray) command for that. Let's assume for now that you have that list of names in another file. Then,

readarray -t names < listfile

would fill the array names with the contents of the file, one entry per line.

Note that in your question, the string in myList also contains backslashes and spaces in front of the second and third directories, which you'd probably want to clean up if you store the list like that. Or you could just store the list as an array directly, like in @James S.'s answer.


Then, the second issue is building the list of arguments to find. There, you also need an array to keep the arguments intact and separate. So,

args=()
for n in "${names[@]}"; do
    args+=(-name "$n" -prune -o)
done

would fill the array args with arguments. You'd then use it as

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

Also, you don't need to repeat the -prune, you can wrap the whole thing in parenthesis and just have -prune once on the outside, e.g. on the command line, you'd use ... "(" -name this -o -name that ")" -prune ... and you could fill the array something like this

args=("(")
for n in "${names[@]}"; do
    args+=(-name "$n" -o)
done
unset "args[${#args[@]} - 1]"  # remove the last -o
args+=( ")" -prune )

See:

ilkkachu
  • 138,973
0

I think you could use the special variable IFS

 #!/bin/bash
OFS=$IFS
IFS=$'\n'

myList="a \ b c \ d"

dirList= stringArray=($myList) for i in "${stringArray[@]}" ; do dirList="$dirList -name $i -prune -o" echo "$dirList" echo " " done IFS=$OFS

This is the given output:

 -name a \ -prune -o

-name a \ -prune -o -name b c \ -prune -o

-name a \ -prune -o -name b c \ -prune -o -name d -prune -o