знайти | 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

робить саме те, що я хочу, він перераховує всі файли поточного каталогу і обчислює ша1 суми (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) або виключити її в пошуку спеціально, але я хотів би зрозуміти, чому вона поводиться так, як це відбувається - що, на мої очі, не так корисно, наприклад, якщо перша команда перевірить, чи файл виводу вже на диску, він ніколи не отримає правильну відповідь ...


8
Це не той xargs, що саме оболонка створює цей файл, тому що перед виконанням будь-якої команди спочатку оболонка перенаправляє всі вхідні, вихідні та трубові, так що при findзапуску вихідний файл вже існує. Використовуйте -execзамість цього:find -maxdepth 1 -type f -exec sh -c 'shasum "$@" > sums.sha1' {} +
— jimmij

@jimmij, це не гарантовано працюватиме, якщо необхідно кілька shвикликів. Зауважте, що вам потрібен аргумент для $0цього {}.
— Stéphane Chazelas

@jimmij Ваша інша відповідь, яка запропонувала tee, зникла? Я спробував це, і він працює добре, я також придушив STDOUT з додаванням 1>/dev/null. Чи щось не було у відповіді або це помилка?
— користувач121391

@ user121391 Стефан зазначив, що іноді може виникнути проблема з умовами гонки, що здається правдою. Я певний час відмінив його, щоб ви могли подивитися, але якщо у вас є багато файлів у списку, ця команда може піти не так.
— jimmij

@jimmij ах, я бачу. Це може бути корисно, якщо ви встановите попередньо попередження про проблеми, тому що я думаю, що це не так відомо, що це може статися. В іншому випадку я прийняв би вашу відповідь у випадках, коли повторювані запуски включають старий файл та Anthon для випадків, коли його слід перезаписати.
— користувач121391

Відповіді:


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 із заданого повного шляху (це не було включено у запитання, але це може допомогти іншим).
— користувач121391

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

Зауважте, що всі ці підходи мають користь для роботи над довільними іменами файлів, у тому числі з пробілами, новими рядками чи чим-небудь іншим.


Я думаю, ви б згадали, що це вирішує будь-які проблеми, з якими ОП матиме імена файлів з новими рядками в них. З іншого боку, якщо sums.sha1вже існує (з попереднього запуску), ваше рішення включить його.
— Антон

Вибачте, я раніше не уточнював: maxdepth використовувався лише в цьому прикладі, я використовую функцію, де користувач / сценарій може надавати будь-які значення, хоча наразі мені потрібна лише глибина 1.
— user121391

@ user121391 див. оновлену відповідь для рекурсивного підходу.
— terdon

Зауважте, що він також намагатиметься перевірити суму інших типів нестандартних файлів, таких як труби, пристрої ... (і посилання на них).
— Стефан Шазелас

Дякую, я особисто використовую sh, але ваша відповідь може допомогти іншим.
— користувач121391

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

Зауважте, що вони виконують одну команду шасума, тож ви можете побачити помилку "Аргумент задовгий", якщо список величезний. Щоб обійти це:

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

Я рекомендую використовувати ./*замість цього, *щоб уникнути можливих проблем з файлом, який називається -.


Я редагував питання з типом оболонки, але ваша відповідь нагадує мені, що я хотів перейти на zsh деякий час тому назад
— ;;

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

В якості альтернативи знаходженню / 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.