我正在创建一个用于复制/校验文件的脚本....运行最新的 Mac OS X/FreeBSD,可以移植到 CentOS、Debian 或 OpenBSD
有关脚本的更多信息:
- 检查源路径是否包含文件/子目录
- 为每个目录/子目录创建文件校验和
- tar/压缩到目标路径
- 检查目标路径中的文件完整性
当然,这是多疑的,因为文件完整性检查是在 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