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


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

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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.