find - 执行并堆叠多个命令

find - 执行并堆叠多个命令

我正在创建一个用于复制/校验文件的脚本....运行最新的 Mac OS X/FreeBSD,可以移植到 CentOS、Debian 或 OpenBSD

有关脚本的更多信息:

  1. 检查源路径是否包含文件/子目录
  2. 为每个目录/子目录创建文件校验和
  3. tar/压缩到目标路径
  4. 检查目标路径中的文件完整性

当然,这是多疑的,因为文件完整性检查是在 HW/HDD 级别进行的,可以通过 SMART 轻松检查,但十年后,我无法检查原始文件的完整性。校验和是在 CF/XD 卡上创建的,它是原始的……您可以随意复制,而不必担心所谓的腐烂位、硬件错误等。

当然也可以使用 rsync,但我不喜欢过时的 MD5/SHA1 校验和,因为可能发生冲突。这需要数小时的工作、“运气”和汗水才能在正确的时间出现在正确的地点并拍摄一张独特的照片……如果你丢失了原始 RAW……它将永远消失,只剩下记忆。

“只有偏执狂才能生存”——安迪·格鲁夫

我有针对步骤 1 的简单工作脚本。脚本如下:

today=`date +%Y-%m-%d`
CHK='shasum -a512'
CHK_OUTPUT=($today)-checksum.txt
find . -type f ! -name  ".*" -maxdepth 1 -exec $CHK {} \; > "$CHK_OUTPUT"

我按预期获得了校验和文件,但问题是“我们可以做得更好吗?”

...cf83e1357eef47417a81a538327af927da3e  ./(2017-07-19)-checksum.txt

我想摆脱烦人的./所以我编写了以下代码...

find ./ -type f ! -name  ".*" -maxdepth 1 -exec bash -c '$CHK $(basename {}) > $CHK_OUTPUT' \;

不幸的是,我收到以下错误

bash: ${CHK_OUTPUT}: ambiguous redirect

另一次尝试

    find ./ -type f ! -name  ".*" -maxdepth 1 -exec bash -c '$CHK $(basename {})' \; > $CHK_OUTPUT

它以某种方式起作用,但结果很奇怪

我没通过 UTFM 和 RTFM,我甚至不知道如何向 Google 询问 :-D

有人可以建议怎样做吗?

问候

大卫

答案1

如何将参数传递给子 shell-exec

使用,您可以使用子 shell 传递复杂命令,正如您正确指出的find那样-exec。但是,您的方法存在一些问题。

由于外部变量$CHK位于单引号内,因此不会展开。您可以采取以下措施来让子 shellexport事先知道此变量属于它:

$ foo=bar
$ find . -type f -exec sh -c 'echo "$foo"' \;
(returns an empty line for every file found)

$ export foo=bar
$ find . -type f -exec sh -c 'echo "$foo"' \;
(returns "bar" for every file found)

导出变量使其成为环境的一部分,子 shell 可以读取该变量。或者,您可以将其作为单独的参数传递给子 shell,这是此处的首选方法:

$ foo=bar
$ find . -type f -exec sh -c 'echo "$0"' "$foo" \;
(returns "bar" for every file found)

当然,您可以继续使用等$1$2并将{}其用作子 shell 的参数,以使用实际文件名。并且不要忘记引用变量。

你的具体情况

您实际上可以简单地将命令重写为:

shasum -a512 * > "$CHK_OUTPUT"

因为shasum足够聪明,可以在一个命令中完成工作,读取多个文件,而无需循环或find。默认情况下,*不包括以点开头的文件(但您可以使用更改它shopt -s dotglob),因此您的find选项是不必要的,尤其是当maxdepth为 1 时。

但是我们假装shasum没那么聪明,这样我就可以给你更多的选择。如果你想使用find,你可以这样处理多个参数:

CHK='shasum -a512'
find ./ -type f ! -name  ".*" -maxdepth 1 -exec \
sh -c '$0 "$(basename "$1")"' "$CHK" {} \; > "$CHK_OUTPUT"

但即便如此,这一切也可以重写为更易读的形式:

today=$(date +%Y-%m-%d)
for f in *; do shasum -a512 "$f" > "($today)-checksum.txt"; done

循环遍历文件通常比使用更容易find,尽管由于扩展而导致可以通过这种方式处理的文件数量受到限制*— 在某些时候它会对于您的命令行来说太长(具体限制取决于您的操作系统和 shell)。

当然,您也可以shopt -s globstar在 Bash ≥ 4.0 中以递归方式执行此操作:

shopt -s globstar
for f in **/*; do …; done

但这又与以下内容相同:

shasum -a512 **/* > "$CHK_OUTPUT"

答案2

更新

不幸的是,下面提到的命令仍然会在没有文件的子目录中创建空的 checksum.txt。

find ./ -type f \( ! -iname ".*" ! -iname "\(????-??-??\)-checksum.txt" \) -maxdepth 1 -exec sh -c '$0 "$(basename "$1")"' "$CHK" {} \; > $CHK_OUTPUT

我不知道为什么,但有一个解决办法

# remove empty checksum.txt
    if [[ -f $CHK_OUTPUT ]] && [[ ! -s $CHK_OUTPUT ]]; then
        rm $CHK_OUTPUT
    fi

=====================================================

最终解决方案...到目前为止似乎有效**

find ./ -type f \( ! -iname ".*" ! -iname "\(????-??-??\)-checksum.txt" \) -maxdepth 1 -exec sh -c '$0 "$(basename "$1")"' "$CHK" {} \; > $CHK_OUTPUT

相关内容