znajdź | xargs shasum tworzy sumę kontrolną samego pliku sumy kontrolnej (przedwcześnie) i kończy się niepowodzeniem podczas sprawdzania

10

Mój problem (w skrypcie z #!/bin/sh) wygląda następująco: Próbuję zsumować wszystkie pliki w katalogu do celów archiwalnych. Plik sumy kontrolnej (w moim przypadku sha1) ze wszystkimi nazwami plików powinien znajdować się w tym samym katalogu. Powiedzmy, że mamy katalog ~/testz plikami f1i f2:.

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

Teraz obliczamy sumy kontrolne za pomocą

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

robi dokładnie to, co chcę, wyświetla listę wszystkich plików bieżącego katalogu i oblicza sumy sha1 (maxdepth można zmienić później). Dane wyjściowe STDOUT to:

f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2

Niestety przy próbie zapisania tego pliku do pliku

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

plik wynikowy wyświetla sumę kontrolną dla siebie:

da39a3ee5e6b4b0d3255bfef95601890afd80709  sums.sha1
f572d396fae9206628714fb2ce00f72e94f2258f  f1
9591818c07e900db7e1e0bc4b884c945e6a61b24  f2  

i dlatego nie powiedzie się później shasum --check, z powodu oczywistego problemu dodatkowej modyfikacji pliku podczas zapisywania ostatniej sumy.

Rozejrzałem się i używając -pflagi dla xargs, dowiedziałem się, że jakoś tworzy plik wyjściowy przed wykonaniem polecenia find, dlatego dodatkowy plik zostanie znaleziony i zostanie sprawdzony ...

Wiem, że jako obejście problemu mogłem zapisać sumę kontrolną w innej lokalizacji (katalog tymczasowy przez mktemp) lub wykluczyć ją w znalezieniu, ale chciałbym zrozumieć, dlaczego zachowuje się tak, jak to działa - co w moich oczach nie jest takie przydatne, na przykład, jeśli pierwsze polecenie sprawdzi, czy plik wyjściowy jest już na dysku, nigdy nie uzyska poprawnej odpowiedzi ...

użytkownik121391
źródło
8
Tak nie jest xargs, to sama powłoka tworzy ten plik, ponieważ przed wykonaniem jakiegokolwiek polecenia najpierw przekierowuje wszystkie dane wejściowe, wyjściowe i potoki, tak że po finduruchomieniu plik wyjściowy już istnieje. -execZamiast tego użyj :find -maxdepth 1 -type f -exec sh -c 'shasum "$@" > sums.sha1' {} +
jimmij
@jimmij, nie gwarantuje się, że zadziała, jeśli shkonieczne jest kilka inwokacji. Zauważ, że potrzebujesz argumentu $0wcześniej {}.
Stéphane Chazelas
@jimmij Twoja inna sugerowana odpowiedź teezniknęła? Próbowałem i działa dobrze, tłumiłem również STDOUT dodając 1>/dev/null. Czy było coś nie tak z odpowiedzią, czy błąd?
user121391,
@ user121391 Stephane zwrócił uwagę, że czasami może występować problem z warunkami wyścigu, co wydaje się prawdą. Usunąłem go na chwilę, abyś mógł zajrzeć, ale jeśli masz wiele plików na liście, to polecenie może się nie udać.
jimmij
@jimmij ah, rozumiem. Może to być pomocne, jeśli poprzedza je ostrzeżenie o problemach, ponieważ myślę, że nie jest tak dobrze znane, że tak się może stać. W przeciwnym razie zaakceptowałbym twoją odpowiedź na sprawy, jeśli cykliczne działania zawierają stary plik i Anthona dla przypadków, w których należy go zastąpić.
user121391

Odpowiedzi:

12

Możesz uniemożliwić dostęp do pliku xargsza pomocą:

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

Aby zapobiec problemom z nazwami plików, które mają spacje lub znaki nowej linii, cytaty lub ukośniki odwrotne, użyłbym jednak:

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

zamiast.

Ma --to na celu uniknięcie problemów z nazwami plików rozpoczynającymi się od -. Nie pomoże to jednak w przypadku pliku o nazwie -. Gdybyś używał -print0zamiast tego -printf '%P\0', nie byłbyś potrzebny --i nie miałby problemu z -plikiem.

Anthon
źródło
Twoje rozwiązanie jest tym, co ostatecznie wykorzystałem. Szczególnie podoba mi się to, że kolejne uruchomienia nie zmieniają pliku sumy kontrolnej i nie wypełniają katalogu. Ponadto w moim skrypcie basenameuzyskiwałem nazwę pliku sums.sha1 z podanej pełnej ścieżki (nie zostało to uwzględnione w pytaniu, ale może pomóc innym).
user121391,
7

Ponieważ używasz -maxdepth 1, zakładam, że nie chcesz rekurencji. Jeśli tak, po prostu zrób to w powłoce:

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

Aby pominąć katalogi, możesz:

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

Jeśli potrzebujesz rekurencji i używasz bash:

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

Zauważ, że wszystkie te podejścia mają tę zaletę, że pracują nad dowolnymi nazwami plików, w tym ze spacjami, znakami nowej linii lub czymkolwiek innym.

terdon
źródło
Myślę, że wspomniałbyś, że to rozwiązuje wszelkie problemy, jakie OP miałby z nazwami plików z nowymi wierszami. Z drugiej strony, jeśli sums.sha1już tam jest (z poprzedniego uruchomienia), twoje rozwiązanie ją uwzględni.
Anthon
Przepraszam, nie wyjaśniłem wcześniej: maxdepth był używany tylko w tym przykładzie, używam funkcji, w której użytkownik / skrypt może podać dowolne wartości, chociaż obecnie potrzebuję tylko głębokości 1.
użytkownik121391
@ user121391 zobacz zaktualizowaną odpowiedź dotyczącą podejścia rekurencyjnego.
terdon
Pamiętaj, że spróbuje również sprawdzić sumę innych typów nieregularnych plików, takich jak potoki, urządzenia ... (i dowiązania symboliczne do nich).
Stéphane Chazelas,
Dziękuję, osobiście korzystam sh, ale twoja odpowiedź może pomóc innym.
user121391,
4

z zsh:

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

Glob zostanie rozszerzony przed dokonaniem przekierowania, więc sums.sha1nie zostanie uwzględniony, jeśli go nie będzie.

Dpolega na dołączeniu plików kropek (plików ukrytych) tak find, jak by to było. .jest wybranie tylko zwykłych plików (takich jak twój -type f).

W sums.sha1każdym razie, aby je wykluczyć, gdyby miało to miejsce:

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

Zauważ, że uruchamiają one jedną komendę shasum, więc możesz zobaczyć błąd „Zbyt długa lista Arg”, jeśli lista jest duża. Aby obejść ten problem:

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

Polecam używanie ./*zamiast, *aby uniknąć potencjalnych problemów z plikiem o nazwie -.

Stéphane Chazelas
źródło
Edytowałem pytanie z rodzajem powłoki, ale twoja odpowiedź przypomina mi, że chciałem przełączyć się na zsh jakiś czas temu ...;)
user121391
1

Jak już stwierdzono w innych odpowiedziach, problemem jest to, że powłoka otwiera się i tworzy sums.sha1plik przed wykonaniem potoku. Możesz użyć programu, spongektóry jest częścią moreutilspakietu wielu dystrybucji. W przeciwieństwie do przekierowania powłoki sponge, przed otwarciem pliku zaczeka, aż wszystko otrzyma. Zwykle jest używany, gdy chcesz zapisać plik, który czytasz w tym samym potoku.

W twoim przypadku używa się go w następujący sposób:

$ find -maxdepth 1 -type f -printf '%P\n' |xargs shasum |sponge sums.sha1
$ cat sums.sha1
31836aeaab22dc49555a97edb4c753881432e01d  B
7d157d7c000ae27db146575c08ce30df893d3a64  A
TimWolla
źródło
0

Alternatywą dla find / xargs itp. Może być sha1deep. Prawdopodobnie jest w innym pakiecie - na moim pudełku jest w pakiecie md5deep.

Jak powiedzieli inni, sums.sha1 jest tworzony przez powłokę jeszcze przed uruchomieniem find. Sztuczka z ! -name sums.sha1do findbędzie działać, jak woli

find -maxdepth 1 -type f -printf '%P\n' | xargs shasum | grep -v ' sums\.sha1$' > sums.sha1
Torinthiel
źródło