найти | 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флаг for xargs, обнаружил, что он каким-то образом создает выходной файл еще до выполнения команды find, поэтому дополнительный файл найден и будет проверен ...

Я знаю, что в качестве обходного пути я мог бы сохранить контрольную сумму в другом месте (через временный каталог mktemp) или исключить ее из функции find, но я хотел бы понять, почему она ведет себя так, как она работает - что, на мой взгляд, не очень полезно, например, если первая команда проверит, находится ли выходной файл на диске, она никогда не получит правильный ответ ...

user121391
источник
8
Это не так xargs, именно сама оболочка создает этот файл, потому что перед выполнением любой команды оболочка перенаправляет все входные, выходные и конвейерные данные, так что при findзапуске выходной файл уже существует. Используйте -execвместо:find -maxdepth 1 -type f -exec sh -c 'shasum "$@" > sums.sha1' {} +
Джимми
@jimmij, это тоже не гарантируется, если необходимо несколько shвызовов. Обратите внимание, что вам нужен аргумент для $0ранее {}.
Стефан Шазелас
@jimmij Ваш другой предложенный ответ teeисчез? Я попробовал, и он работает нормально, я также подавил STDOUT с добавлением 1>/dev/null. Было ли что-то не так с ответом или это ошибка?
user121391
@ user121391 Стефан отметил, что иногда могут быть проблемы с расой, что кажется правдой. Я удалил его на некоторое время, чтобы вы могли посмотреть, но если у вас много файлов в списке, эта команда может пойти не так.
Джимми
@jimmij ах, понятно. Было бы полезно, если бы вы добавили в качестве префикса предупреждение о проблемах, потому что я думаю, что не так хорошо известно, что это может произойти. В противном случае, я бы принял ваш ответ для случаев, если повторяющиеся запуски включают старый файл и файл Anthon для случаев, когда он должен быть перезаписан.
user121391

Ответы:

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
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оно уже есть (из предыдущего прогона), ваше решение включит его.
Антон
Извините, я не уточнил ранее: maxdepth использовался только в этом примере, я использую функцию, в которой пользователь / скрипт может предоставлять любые значения, хотя в настоящее время мне нужна только глубина 1.
user121391
@ user121391 см. обновленный ответ для рекурсивного подхода.
Terdon
Обратите внимание, что он также попытается проверить контрольные суммы других типов нестандартных файлов, таких как каналы, устройства ... (и символические ссылки на них).
Стефан Шазелас
Спасибо, лично я пользуюсь sh, но ваш ответ может помочь другим.
user121391
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 list too long», если список огромен. Чтобы обойти это:

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

Я бы рекомендовал использовать ./*вместо, *чтобы избежать потенциальных проблем с файлом с именем -.

Стефан Шазелас
источник
Я редактировал вопрос с типом оболочки, но ваш ответ напоминает мне, что я хотел переключиться на zsh некоторое время назад ...;)
user121391
1

Как уже говорилось в других ответах, проблема в том, что оболочка открывается и создает sums.sha1файл перед выполнением вашего конвейера. Вы можете использовать программу, spongeкоторая входит в moreutilsпакет многих дистрибутивов. В отличие от оболочки перенаправление spongeбудет ждать, пока не получит все, прежде чем открывать файл. Обычно используется, когда вы хотите записать файл, который вы читаете в том же конвейере.

В вашем случае это используется так:

$ find -maxdepth 1 -type f -printf '%P\n' |xargs shasum |sponge sums.sha1
$ cat sums.sha1
31836aeaab22dc49555a97edb4c753881432e01d  B
7d157d7c000ae27db146575c08ce30df893d3a64  A
TimWolla
источник
0

В качестве альтернативы find / xargs и т. Д. Вы можете использовать sha1deep. Хотя, вероятно, он находится в другом пакете - на моей коробке он входит в пакет md5deep.

Как уже говорили другие, sums.sha1 создается оболочкой еще до начала поиска. Хитрость в том, ! -name sums.sha1чтобы findбудет работать, как будет

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum | grep -v ' sums\.sha1$' > sums.sha1
Torinthiel
источник