Building up a command string for find

2019-07-21 18:52发布

I'm trying to parse the android source directory and i need to extract all the directory names excluding certain patterns. If you notice below., for now i included only 1 directory to the exclude list, but i will be adding more.,

The find command doesn't exclude the directory with name 'docs'.

The commented out line works., but the other one doesn't. For easy debugging, i included the min and maxdepth which i would remove later.

Any comments or hints on why it doesn't work?

#! /bin/bash
ANDROID_PATH=$1
root=/
EXCLUDES=( doc )
cd ${root}
for dir in "${EXCLUDES[@]}"; do
    exclude_name_cmd_string=${exclude_name_cmd_string}$(echo \
                            "-not -name \"${dir}*\" -prune")
done
echo -e ${exclude_name_cmd_string}
custom_find_cmd=$(find ${ANDROID_PATH} -mindepth 1 -maxdepth 1 \
                    ${exclude_name_cmd_string} -type d)
#custom_find_cmd=$(find ${ANDROID_PATH} -mindepth 1 -maxdepth 1 \
#                    -not -name "doc*" -prune -type d)
echo ${custom_find_cmd} 

标签: bash shell
3条回答
三岁会撩人
2楼-- · 2019-07-21 19:09

Building up a command string with possibly-quoted arguments is a bad idea. You get into nested quoting levels and eval and a bunch of other dangerous/confusing syntactic stuff.

Use an array to build the find; you've already got the EXCLUDES in one.

Also, the repeated -not and -prune seems weird to me. I would write your command as something like this:

excludes=()

for dir in "${EXCLUDES[@]}"; do
  excludes+=(-name "${dir}*" -prune -o) 
done

find "${ANDROID_PATH}" -mindepth 1 -maxdepth 1 "${excludes[@]}" -type d -print

The upshot is, you want the argument to -name to be passed to find as a literal wildcard that find will expand, not a list of files returned by the shell's expansion, nor a string containing literal quotation marks. This is very hard to do if you try to build the command as a string, but trivial if you use an array.

Friends don't let friends build shell commands as strings.

查看更多
别忘想泡老子
3楼-- · 2019-07-21 19:09

Found the problem. Its with the escape sequence in the exclude_name_cmd_string. Correct syntax should have been

exclude_name_cmd_string=${exclude_name_cmd_string}$(echo \
                        "-not -name ${dir}* -prune")
查看更多
神经病院院长
4楼-- · 2019-07-21 19:26

When I run your script (named fin.sh) as:

bash -x fin.sh $HOME/tmp

one of the lines of trace output is:

find /Users/jleffler/tmp -mindepth 1 -maxdepth 1 -not -name '"doc*"' -prune -type d

Do you see the single quotes around the double quotes? That's bash trying to be helpful. I'm guessing that your "doesn't work" problem is that you still get directories under doc* included in the output; other than that, it seems to work for me.

How to fix that?

...it seems you've found a way to fix that...I'm not sure I'd trust it with a Bourne shell (but the Korn shell seems to agree with Bash), but it looks like it might work with Bash. I'm pretty sure this is something that changed during the last 30 years or so, but it is hard to prove that; getting hands on the old code is not easy.

I also wonder whether you need repeated -prune options if you have repeated excluded directories; I'm not sufficiently familiar with -prune to be sure.

查看更多
登录 后发表回答