找到 xargs shasum(过早)创建校验和文件本身的校验和,并且在检查时失败


10

我的问题(在带有的脚本中#!/bin/sh)如下:我尝试出于归档目的对目录中的所有文件进行校验和。具有所有文件名的校验和(在我的情况下为sha1)文件应位于同一目录中。假设我们有一个~/test包含文件f1和的目录f2

mkdir ~/test
cd ~/test
echo "hello" > f1
echo "world" > f2

现在用

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum

完全符合我的要求,它仅列出当前目录的所有文件并计算sha1和(maxdepth可能在以后更改)。STDOUT上的输出为:

f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2

不幸的是,当尝试使用

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum > sums.sha1

生成的文件将显示自身的校验和:

da39a3ee5e6b4b0d3255bfef95601890afd80709  sums.sha1
f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2  

并因此在以后失败shasum --check,这是因为在保存最后一笔款项时显然会进行其他文件修改的问题。

我环顾四周,并使用-p标志xargs,我发现它甚至在执行find命令之前就以某种方式创建了输出文件,因此找到了附加文件并将其校验和...

我知道作为一种解决方法,我可以将校验和保存到另一个位置(通过进行临时目录mktemp),或者将其排除在find之外,但是我想了解为什么它的行为方式如此-在我看来这没什么用,例如,如果第一个命令将检查输出文件是否已经在磁盘上,它将永远不会获得正确的答案...


8
不是xargs,而是shell本身创建了该文件,因为执行任何命令之前, shell都会重定向所有输入,输出和管道,因此find启动时输出文件已经存在。使用-exec来代替:find -maxdepth 1 -type f -exec sh -c 'shasum "$@" > sums.sha1' {} +
jimmij

@jimmij,如果需要多次sh调用,也不能保证能正常工作。请注意,您需要一个$0before 的参数{}
斯特凡Chazelas

@jimmij您建议的其他答案tee已消失?我尝试了一下,效果很好,还添加了来抑制STDOUT 1>/dev/null。答案是否有问题或是错误?
user121391 2015年

@ user121391 Stephane指出,有时可能会出现竞争状况问题,这似乎是事实。我暂时删除了它,以便您查看,但是如果列表上有很多文件,该命令可能会出错。
jimmij 2015年

@jimmij啊,我明白了。如果在其前面加上有关问题的警告,可能会有所帮助,因为我认为这种情况可能不太了解。否则,如果重复运行包含旧文件以及应覆盖旧文件的Anthon文件,我会接受您的回答。
user121391

Answers:


12

您可以xargs使用以下方法阻止文件到达:

find . -maxdepth 1 -type f ! -name sums.sha1 -printf '%P\n' |
  xargs -r shasum -- > sums.sha1

为了防止文件名出现空格,换行符或引号或反斜杠的问题,我将使用:

find . -maxdepth 1 -type f ! -name sums.sha1 -printf '%P\0' |
  xargs -r0 shasum -- > sums.sha1

代替。

--是为了避免与同开始的文件名的问题-。但是,对于名为的文件没有帮助-。如果您使用-print0而不是-printf '%P\0',则不需要,--并且-文件不会有问题。


您的解决方案就是我最终使用的解决方案。我特别喜欢后续的运行不会重新散列校验和文件并扩大目录。另外,在我的脚本中,我曾经basename从给定的完整路径获取sums.sha1文件名(问题中未包括该文件名,但可能会对其他人有所帮助)。
user121391 2015年

7

由于您正在使用-maxdepth 1,所以我假设您不需要递归。如果是这样,只需在外壳中执行以下操作:

for f in ~/test/*; do
    shasum -- "$f"
done > sums.sha1

要跳过目录,可以执行以下操作:

for f in ~/test/*; do
    [ ! -d "$f" ] && shasum -- "$f"
done > sums.sha1

如果您确实需要递归并且正在使用bash,请执行以下操作:

shopt -s globstar
for f in ~/test/**; do
    [ ! -d "$f" ] && shasum -- "$f"
done > sums.sha1

请注意,所有这些方法都具有处理任意文件名(包括带有空格,换行符或其他内容的文件名)的好处。


我想您会提到,这可以解决OP在文件名中带有换行符的所有问题。另一方面,如果sums.sha1已经存在(来自先前的运行),则您的解决方案将合并它。
Anthon 2015年

对不起,我没有澄清之前:在这个例子中只使用了MAXDEPTH,我使用的功能中,用户/脚本可以提供任何值,但目前我只需要深度为1
user121391

@ user121391请参阅更新的答案以获取递归方法。
terdon

请注意,它还将尝试校验和其他类型的非常规文件,例如管道,设备...(以及指向它们的符号链接)。
斯特凡Chazelas

谢谢您,我个人正在使用sh,但您的回答可能会对他人有所帮助。
user121391 2015年

4

zsh

shasum -- *(D.) > sums.sha1

全局文件将在进行重定向之前扩展,因此sums.sha1如果不存在该文件,则将不包括在内。

D将包括点文件(隐藏文件)find.是仅选择常规文件(如-type f)。

sums.sha1无论如何要排除它首先出现在这里:

setopt extendedglob # best in ~/.zshrc
shasum -- ^sums.sha1(D.) > sums.sha1

请注意,这些命令运行一个 shasum命令,因此如果列表很大,您可能最终会看到“ Arg列表太长”错误。要解决此问题:

autoload zargs
zargs -e/ -- *(D.) / shasum > sums.sha1

我建议您使用./*而不是*避免使用名为的文件的潜在问题-


我用外壳类型编辑了该问题,但您的回答使我想起我想在一段时间前切换到zsh ...;)
user121391 2015年

1

正如其他答案所述,问题是sums.sha1在执行管道之前,外壳程序会打开并创建文件。您可以使用该程序sponge,它是moreutils许多发行版软件包的一部分。与之相反,shell重定向sponge将等到它收到所有内容后再打开文件。当您要编写在同一管道中读取的文件时,通常使用它。

在您的情况下,它的用法如下:

$ find -maxdepth 1 -type f -printf '%P\n' |xargs shasum |sponge sums.sha1
$ cat sums.sha1
31836aeaab22dc49555a97edb4c753881432e01d  B
7d157d7c000ae27db146575c08ce30df893d3a64  A

0

作为find / xargs等的替代方法,您可能需要sha1deep。但是,它可能位于不同的软件包中-在我的盒子中,它位于md5deep软件包中。

就像其他人所说的一样,sums.sha1甚至在find开始之前由外壳创建。一招! -name sums.sha1,以find将工作,如将

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum | grep -v ' sums\.sha1$' > sums.sha1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.