无法在查找结果的 -exec 命令中将值分配给数组

无法在查找结果的 -exec 命令中将值分配给数组

我编写了以下命令,将查找结果分配到数组中,以删除 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 执行分配。

对于bash4.4+,你可以这样做:

readarray -td '' F_ARR < <(find "$CDIR" ! -type d -mtime +7 -print0)

如果你find不支持-print0,你可以用 替换它-exec printf '%s\0' {} +

(对于旧版本的bash,请参阅在这个类似问题的答案中你可以如何做到这一点)。

在这里,您还可以使用zshshell 来代替:

f_arr=($CDIR/**/*(NDm+7^/))

与上述的区别在于:

  • 文件列表已排序,您可以添加oNglob 限定符来禁用排序
  • $CDIR即使它已经存在超过 8 天,它本身也不会被包括在内。
  • 如果$CDIR是目录的符号链接,zsh仍然会下降到该目录。

由于您使用 和 来标记您的问题bashksh请注意,ksh 有不同的实现和变体(ksh88、ksh93、即将推出的 ksh2020(最初)由 David Korn、pdksh 及其衍生物(例如 OpenBSDsh或 )mksh,您可以使用可以添加zsh的ksh模拟模式)。

大多数bash功能确实来自ksh88ksh93,少数来自zshmksh。是少数原生且在其他地方找不到的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

相关内容