![无法在查找结果的 -exec 命令中将值分配给数组](https://linux22.com/image/160931/%E6%97%A0%E6%B3%95%E5%9C%A8%E6%9F%A5%E6%89%BE%E7%BB%93%E6%9E%9C%E7%9A%84%20-exec%20%E5%91%BD%E4%BB%A4%E4%B8%AD%E5%B0%86%E5%80%BC%E5%88%86%E9%85%8D%E7%BB%99%E6%95%B0%E7%BB%84.png)
我编写了以下命令,将查找结果分配到数组中,以删除 7 天后旧的文件。
F_ARR[0]=''
unset F_ARR
find "$CDIR" ! -type d -mtime +7 -exec sh -c '
for pathname; do
F_ARR+=("$pathname")
done' sh {} +
echo ${#F_ARR[@]}
但是,当我打印数组 F_ARR 的长度时,它显示 0。我在代码中犯了错误吗?
答案1
find ... -exec sh -c 'sh code' {} +
在 bash/ksh shell 中,在运行一个或多个命令调用find
的新进程中运行命令。sh
只能sh code
影响sh
解释该代码的调用的变量(顺便说一句,sh
并不意味着支持数组)。
如果要将找到的文件的路径存储find
到当前 shell 的数组中,则必须由该 shell 执行分配。
对于bash
4.4+,你可以这样做:
readarray -td '' F_ARR < <(find "$CDIR" ! -type d -mtime +7 -print0)
如果你find
不支持-print0
,你可以用 替换它-exec printf '%s\0' {} +
。
(对于旧版本的bash
,请参阅在这个类似问题的答案中你可以如何做到这一点)。
在这里,您还可以使用zsh
shell 来代替:
f_arr=($CDIR/**/*(NDm+7^/))
与上述的区别在于:
- 文件列表已排序,您可以添加
oN
glob 限定符来禁用排序 $CDIR
即使它已经存在超过 8 天,它本身也不会被包括在内。- 如果
$CDIR
是目录的符号链接,zsh
仍然会下降到该目录。
由于您使用 和 来标记您的问题bash
,ksh
请注意,ksh 有不同的实现和变体(ksh88、ksh93、即将推出的 ksh2020(最初)由 David Korn、pdksh 及其衍生物(例如 OpenBSDsh
或 )mksh
,您可以使用可以添加zsh的ksh模拟模式)。
大多数bash
功能确实来自ksh88
和ksh93
,少数来自zsh
和mksh
。是少数原生且在其他地方找不到的readarray
功能之一。bash
ksh93 确实在 1993 年添加了一个-d
选项read
,后来被 bash (2000) zsh (2003) 和 mksh (2011) 复制,但只有 bash、zsh、mksh 和 ksh2020 可以使用它与空分隔符来处理 NUL 分隔记录。 ksh86 (1986) 中添加了进程替换,但是基于 pdksh 的 ksh 变体都不支持它,因此上面提到的旧版本的方法bash
在任何版本的 ksh 中都不起作用(除了 zsh 的 ksh 模拟模式)。
在 ksh88、ksh93 和基于 pdksh 的 shell 中,一种选择是对 的输出进行后处理,find
以便生成可以定义该数组并使用以下命令评估该代码的 shell 代码eval
:
eval set -A F_ARR "$(
LC_ALL=C find "$CDIR" ! -type d -exec awk -v q="'" -- '
BEGIN {
for (i = 1; i < ARGC; i++) {
gsub(q, q "\\" q q, ARGV[i])
printf " %s", q ARGV[i] q
}
exit
}' {} +
)"
(这里假设$CDIR
不以+
或开头-
。如果可以,在某些ksh
实现中,您可以使用set -A F_ARR --
。无论如何,如果它以 开头-
,find
将会被阻塞)。
set -A F_ARR
通过替换为,可以将相同的方法用于任何 POSIX sh set --
,然后填充 POSIX shell 的一个数组:"$@"
。
答案2
这是预期的——请参阅联机帮助页有关详细信息,例如此报价All commands of a pipeline are executed in separate subshells
以及以下常见问题解答条目:
Something is going wrong with my while...read loop
Most likely, you've encountered the problem in which the shell runs all
parts of a pipeline as subshell. The inner loop will be executed in a
subshell and variable changes cannot be propagated if run in a pipeline:
bar | baz | while read foo; do ...; done
Note that exit in the inner loop will only exit the subshell and not the
original shell. Likewise, if the code is inside a function, return in
the inner loop will only exit the subshell and won't terminate the func‐
tion.
Use co-processes instead:
bar | baz |&
while read -p foo; do ...; done
exec 3>&p; exec 3>&-
If read is run in a loop such as while read foo; do ...; done then lead‐
ing whitespace will be removed (IFS) and backslashes processed. You
might want to use while IFS= read -r foo; do ...; done for pristine I/O.
Similarly, when using the -a option, use of the -r option might be pru‐
dent (“read -raN-1 arr <file”); the same applies for NUL-terminated
lines:
find . -type f -print0 |& \
while IFS= read -d '' -pr filename; do
print -r -- "found <${filename#./}>"
done
因此,让我们转换您的代码(还将前两行替换为正确清除数组的代码):
set -A F_ARR
find "$CDIR" ! -type d -mtime +7 -print0 |&
while IFS= read -d '' -pr pathname; do
F_ARR+=("$pathname")
done
echo ${#F_ARR[@]}
这需要一个查找(1)支持该选项,并且在执行方面-print0
足够新以支持NUL 终止行。如果您的 Korn Shell 较旧,请不要使用它(但它也不支持任何一个,所以很可能它已经足够新了)。mksh
-d ''
+=(…)
全面披露:我是姆克什开发商。
答案3
我想了一会儿,决定主要保留你的编码方式,
我不熟悉 ksh,所以我必须在 bash 中编码,
抱歉,如果你需要翻译
需要一个临时文件
,因为for
两者 while
都会在管道中生成子 shell,因此无法传递数组值
您可以更改文件模式,以便将mktemp
临时文件放在不需要处理的目录中,
将 while 循环中的 echo 部分替换为代码
#!/bin/bash
i=0
tmp=`mktemp /tmp/findingXXXXXX`
find "$CDIR" ! -type d -mtime +7 |awk '{print "F_ARR["(NR-1)"]="$0}' >$tmp
. $tmp
rm -f $tmp
while [ "${F_ARR[$i]}" != "" ];
do
echo i=$i ${F_ARR[$i]}
((i++))
done