我正在尝试find
在目录上运行命令并根据找到的项目生成输出文件。
这是我到目前为止所拥有的:
docker2:~/src/docker# cat test.sh
#!/bin/sh
find /data -type d -mindepth 1 -maxdepth 1 \( ! -name "." \) -exec sh -c "\
echo \"Full path: {}\"; echo \"Basename: `basename {}`\"
" \;
然而,这个命令输出如下:
Full path: /data/nc
Basename: /data/nc
Full path: /data/src
Basename: /data/src
Full path: /data/sql0
Basename: /data/sql0
Full path: /data/proxy
Basename: /data/proxy
我知道反引号应该有效:
docker2:~/src/docker# cat test.sh
#!/bin/sh
find /data -type d -mindepth 1 -maxdepth 1 \( ! -name "." \) -exec sh -c "\
echo \"Date: `date`\"; echo \"Full path: {}\"; echo \"Basename: `basename {}`\"
" \;
给出:
Date: Wed Dec 18 22:56:32 UTC 2019
Full path: /data/nc
Basename: /data/nc
Date: Wed Dec 18 22:56:32 UTC 2019
Full path: /data/src
Basename: /data/src
...
而且我在变量方面也遇到了麻烦:
#!/bin/sh
find /data -type d -mindepth 1 -maxdepth 1 \( ! -name "." \) -exec sh -c "\
export THIS_DIR={}; THIS_DIRECTORY={}; echo \"$THIS_DIR $THIS_DIRECTORY\";
" \;
该命令的输出为空,. 中的每个目录都有一个空行/data
。这意味着即使导出也不会存储变量。
我究竟做错了什么?我猜这与某种奇怪的嵌套或操作顺序或一些奇怪的 shell 代码有关。
答案1
这是预期的,因为您的`basename {}`
内容用双引号引起来。这将使其在调用之前执行find
,这将用字符串替换命令替换{}
。这也是后面示例中的一个问题,变量在实际设置之前会被扩展。
当您使用 时sh -c
,它作为参数的脚本-c
实际上应该始终用单引号引起来,因为这使得脚本内的引用更简单。内联脚本的任何参数都应在其命令行上传递,而不是作为注入到代码中的文本(请参阅“是否可以安全地使用“find -exec sh -c”?“出于原因)。就您而言:
#!/bin/sh
find /data -type d -mindepth 1 -maxdepth 1 -exec sh -c '
echo "Full path: $1"
echo "Basename: $(basename "$1")"' sh {} \;
或者,使用几个变量(并切换到 using printf
):
#!/bin/sh
find /data -type d -mindepth 1 -maxdepth 1 -exec sh -c '
pathname=$1
filename=$(basename "$pathname")
printf "Full path: %s\n" "$pathname"
printf "Basename: %s\n" "$filename"' sh {} \;
当您使用 运行内联脚本时sh -c
,传递的参数将放置在$0
、等中$1
。$2
由于$0
通常是 shell 或脚本的名称,sh
因此我们传递该字符串,然后将找到的路径名传递为$1
。
或者,更有效的是,使用尽可能少的调用sh -c
,并使用参数替换:
#!/bin/sh
find /data -type d -mindepth 1 -maxdepth 1 -exec sh -c '
for pathname do
printf "Full path: %s\n" "$pathname"
printf "Basename: %s\n" "${pathname##*/}"
done' sh {} +
在这里,find
安排使用尽可能多的参数(尽可能少的次数)来调用内联脚本。在每次调用中,$0
都会像以前一样设置为sh
,而其他位置参数则从找到的路径名中获取它们的值。循环迭代传递的路径名($0
不是位置参数,因此不会迭代)。
我假设您的! -name .
测试是为了避免找到/data
目录本身。这不是必需的,因为它已经被跳过-mindepth 1
(/data
路径位于“深度 0”,因为它是搜索路径的顶部)。另外,find
不会从目录中返回条目,因此如果您未使用过,.
则测试不会删除。在这种情况下,您可以改为使用./data
-mindepth 1
! -path /data
因为您只是迭代单个目录中的目录,并且因为您已经使用了巴什标签,并且由于您不习惯find
对时间戳等进行任何花哨的测试,因此您可以执行基本的 shell 循环:
#!/bin/bash
shopt -s dotglob nullglob
for pathname in /data/*; do
if [[ -d $pathname ]] && [[ ! -L $pathname ]]; then
printf 'Full path: %s\n' "$pathname"
printf 'Basename: %s\n' "${pathname##*/}"
fi
done
在这里,在打印有关路径名和名称的信息之前,会显式测试 中的名称/data
是否为目录。中的shelldotglob
选项bash
允许*
通配模式匹配隐藏名称,并且如果为空或模式不匹配任何内容,则nullglob
完全阻止循环形式运行。/data
看 ”了解“find”的 -exec 选项“有关使用find
with 的一般信息-exec
。