Używanie wygenerowanej listy nazw plików jako listy argumentów - ze spacjami


16

Próbuję wywołać skrypt z listą nazw plików zebranych przez find. Nic specjalnego, po prostu coś takiego:

$ myscript `find . -name something.txt`

Problem polega na tym, że niektóre ścieżki zawierają spacje, więc przy interpretacji argumentów dzielą się one na dwie nieprawidłowe nazwy. Zazwyczaj otaczałbym te nazwiska cudzysłowami, ale tutaj są one wstawiane przez rozszerzenie cudzysłowu. Próbowałem filtrować dane wyjściowe findi otaczać każdą nazwę pliku cudzysłowami, ale zanim bash je zobaczy, jest już za późno, aby je rozebrać i są one traktowane jako część nazwy pliku:

$ myscript `find . -name something.txt | sed 's/.*/"&"/'`
No such file or directory: '"./somedir/something.txt"'

Tak, to są zasady dotyczące przetwarzania wiersza poleceń, ale jak go obejść?

To zawstydzające, ale nie potrafię znaleźć odpowiedniego podejścia. W końcu wymyśliłem, jak to zrobić xargs -0 -n 10000... ale jest to tak brzydki hack, że wciąż chcę zapytać: jak zacytować wyniki rozwinięcia cudzysłowu lub uzyskać ten sam efekt w inny sposób?

Edit: Byłem zdezorientowany o tym, że xargs robi zebrać wszystkie argumenty w jednym liście argumentów, chyba że jest to powiedziano inaczej lub ograniczenia systemowe mogą być przekroczone. Dziękujemy wszystkim za wyprostowanie mnie! Inni, pamiętajcie o tym, czytając zaakceptowaną odpowiedź, ponieważ nie została ona wskazana bezpośrednio.

Przyjąłem odpowiedź, ale moje pytanie pozostaje: Czy nie ma sposobu na ochronę przestrzeni w $(...)rozszerzeniu wstecznym (lub )? (Zauważ, że zaakceptowane rozwiązanie jest odpowiedzią bez bashu).


Myślę, że musisz zmienić to, co powłoka wykorzystuje jako separatory nazw plików (na przykład, grając z wartością IFS, jednym z możliwych sposobów jest IFS="nowa linia "). Ale czy istnieje potrzeba wykonania skryptu dla wszystkich nazw plików? Jeśli nie, rozważ użycie polecenia find, aby wykonać skrypt dla każdego pliku.
njsg

Zmiana IFS to świetny pomysł, nie pomyślałem o tym! Nie jest to praktyczne do użycia z linii poleceń, ale nadal. :-) I tak, celem jest przekazanie wszystkich argumentów do tej samej inwokacji mojego skryptu.
Alexis

Odpowiedzi:


12

Możesz wykonać następujące czynności, używając niektórych implementacji findi xargspodobnych.

$ find . -type f -print0 | xargs -r0 ./myscript

lub standardowo po prostu find:

$ find . -type f -exec ./myscript {} +

Przykład

Powiedz, że mam następujący przykładowy katalog.

$ tree
.
|-- dir1
|   `-- a\ file1.txt
|-- dir2
|   `-- a\ file2.txt
|-- dir3
|   `-- a\ file3.txt
`-- myscript

3 directories, 4 files

Powiedzmy teraz, że mam to ./myscript.

#!/bin/bash

for i in "$@"; do
    echo "file: $i"
done

Teraz, gdy uruchomię następujące polecenie.

$ find . -type f -print0 | xargs -r0 ./myscript 
file: ./dir2/a file2.txt
file: ./dir3/a file3.txt
file: ./dir1/a file1.txt
file: ./myscript

Lub kiedy używam drugiego formularza w ten sposób:

$ find . -type f -exec ./myscript {} +
file: ./dir2/a file2.txt
file: ./dir3/a file3.txt
file: ./dir1/a file1.txt
file: ./myscript

Detale

znajdź + xargs

Powyższe 2 metody, choć wyglądają inaczej, są zasadniczo takie same. Pierwszym z nich jest pobranie wyniku z find, podzielenie go za pomocą NULLs ( \0) za pomocą -print0przełącznika find. xargs -0Jest specjalnie zaprojektowana, aby wejście to jest podzielone za pomocą wartości null. Że niestandardowe składnia została wprowadzona przez GNU findi xargsale jest również obecnie w kilku innych podobnych najnowszych BSD. -rOpcja jest wymagana, aby uniknąć wywoływania myscriptjeżeli findstwierdzi nic z GNU find, ale nie z BSD.

UWAGA: Całe to podejście opiera się na fakcie, że nigdy nie przejedziesz struny, która jest wyjątkowo długa. Jeśli tak, to drugie wywołanie ./myscriptzostanie rozpoczęte wraz z pozostałymi wynikami wyszukiwania.

znajdź za pomocą +

Jest to standardowy sposób (choć został dodany stosunkowo niedawno (2005) do implementacji GNU find). Możliwość robienia tego, co robimy, xargsjest dosłownie wbudowana find. Więc findznajdzie listę plików, a następnie przekaże tę listę tylu argumentów, ile może zmieścić się w poleceniu określonym później -exec(zwróć uwagę, że w tym przypadku {}może być tylko przed +chwilą), uruchamiając polecenia kilka razy, jeśli to konieczne.

Dlaczego nie ma cytatu?

W pierwszym przykładzie używamy skrótu, całkowicie unikając problemów z cytowaniem, używając NULL do oddzielenia argumentów. Gdy xargspodana jest ta lista, nakazuje się podział na NULL skutecznie chroniąc nasze indywidualne atomy dowodzenia.

W drugim przykładzie trzymamy wyniki wewnętrznie, findwięc wie, czym jest każdy atom pliku, i zagwarantuje, że odpowiednio z nimi poradzimy, unikając w ten sposób nikogo, kto by je cytował.

Maksymalny rozmiar wiersza poleceń?

To pytanie pojawia się od czasu do czasu, więc jako bonus dodam je do tej odpowiedzi, głównie po to, by znaleźć je w przyszłości. Możesz użyć, xargsaby zobaczyć, jaki jest limit środowiska:

$ xargs --show-limits
Your environment variables take up 4791 bytes
POSIX upper limit on argument length (this system): 2090313
POSIX smallest allowable upper limit on argument length (all systems): 4096
Maximum length of command we could actually use: 2085522
Size of command buffer we are actually using: 131072

1
Dzięki, ale muszę przekazać wszystkie argumenty do tego samego wywołania mojego skryptu. To jest w opisie problemu, ale chyba nie wyjaśniłem, że nie jest to przypadkowe.
Alexis

@alexis - przeczytaj ponownie odpowiedzi, przekazują wszystkie argumenty do pojedynczego wywołania skryptu.
slm

A niech mnie! Nie wiedziałem o +argumentach przeciwko find(a ty też używasz +prozy, więc za pierwszym razem przegapiłem twoje wyjaśnienie). Ale bardziej do rzeczy, źle zrozumiałem, co xargsdomyślnie !!! Przez trzy dekady używania Unixa do tej pory nie miałem z niego żadnego zastosowania, ale wydawało mi się, że znam mój zestaw narzędzi ...
Alexis

@alexis - pomyślałem, że przegapiłeś to, co mówiliśmy. Tak, xargsto diabeł rozkazu. Musisz to przeczytać i findwielokrotnie otwierać strony podręcznika, aby sprawdzić, co potrafią. Maj przełączników jest przeciwny do siebie, co powoduje zamieszanie.
slm

@alexis - także jeszcze jedna rzecz, którą należy dodać do skrzynki narzędzi, nie używaj cudzysłowów / backticków do uruchamiania zagnieżdżonych poleceń, $(..)zamiast tego użyj teraz. Automatycznie obsługuje zagnieżdżanie cudzysłowów itp. Backticks są przestarzałe.
slm

3
find . -name something.txt -exec myscript {} +

Powyżej findznajduje wszystkie pasujące nazwy plików i podaje je jako argumenty myscript. Działa to z nazwami plików niezależnie od spacji i innych nieparzystych znaków.

Jeśli wszystkie nazwy plików mieszczą się w jednym wierszu, skrypt myscript jest wykonywany raz. Jeśli lista jest zbyt długa, aby mogła ją obsłużyć powłoka, find w razie potrzeby uruchomi myscript wiele razy.

WIĘCEJ: ile plików mieści się w linii poleceń? man findmówi, że findbuduje to wiersze poleceń „w taki sam sposób, jak xargs buduje swoje”. I man xargsże limity są zależne od systemu i że można je ustalić, uruchamiając xargs --show-limits. ( getconf ARG_MAXjest również możliwość). W Linuksie limit wynosi zwykle (ale nie zawsze) około 2 milionów znaków na linię poleceń.


2

Kilka dodatków do dobrej odpowiedzi @ slm.

Ograniczenie wielkości argumentów dotyczy execve(2)wywołania systemowego (w rzeczywistości dotyczy to skumulowanego rozmiaru argumentów oraz ciągów i wskaźników środowiska). Jeśli myscriptjest napisany w języku, który może interpretować twoja powłoka, być może nie musisz go uruchamiać , możesz po prostu go zinterpretować bez konieczności wykonywania innego tłumacza.

Jeśli uruchomisz skrypt jako:

(. myscript x y)

To jest jak:

myscript x y

Tyle że jest interpretowane przez dziecko bieżącej powłoki, zamiast jej wykonania (co ostatecznie wiąże się z wykonaniem sh (lub cokolwiek, co określa linia she-bang, jeśli taka istnieje) z jeszcze większą liczbą argumentów).

Oczywiście nie można używać find -exec {} +tego .polecenia, ponieważ .jest to wbudowane polecenie powłoki, które musi być wykonywane przez powłokę, a nie przez find.

Dzięki zshjest to łatwe:

IFS=$'\0'
(. myscript $(find ... -print0))

Lub:

(. myscript ${(ps:\0:)"$(find ... -print0)"}

Mimo zshto nie będziesz potrzebować, findponieważ większość jego funkcji jest wbudowana w zshglobbing.

bashzmienne nie mogą jednak zawierać znaków NUL, więc musisz znaleźć inny sposób. Jednym ze sposobów może być:

files=()
while IFS= read -rd '' -u3 file; do
  files+=("$file")
done 3< <(find ... -print0)
(. myscript "${files[@]}")

Możesz również użyć rekurencyjnego globowania w stylu zsh z globstaropcją w wersji bash4.0 i nowszych:

shopt -s globstar failglob dotglob
(. myscript ./**/something.txt)

Zauważ, że **następowały dowiązania symboliczne do katalogów, dopóki nie zostało to naprawione w bash4.3. Pamiętaj też, że bashnie implementuje zshkwalifikatorów globowania, więc nie uzyskasz wszystkich jego funkcji find.

Inną alternatywą byłoby użycie GNU ls:

eval "files=(find ... -exec ls -d --quoting-style=shell-always {} +)"
(. myscript "${files[@]}")

Powyższych metod można również użyć, jeśli chcesz mieć pewność, że myscriptzostanie wykonane tylko raz (błąd, jeśli lista argumentów jest zbyt duża). W najnowszych wersjach Linuksa możesz podnieść, a nawet znieść to ograniczenie na liście argumentów za pomocą:

ulimit -s 1048576

(Rozmiar stosu 1GiB, którego jedna czwarta może być wykorzystana do listy arg + env).

ulimit -s unlimited

(bez limitu)


1

W większości systemów istnieje ograniczenie długości wiersza poleceń przekazywanego do dowolnego programu za pomocą xargslub -exec command {} +. Od man find:

-exec command {} +
      This  variant  of the -exec action runs the specified command on
      the selected files, but the command line is built  by  appending
      each  selected file name at the end; the total number of invoca
      tions of the command will  be  much  less  than  the  number  of
      matched  files.   The command line is built in much the same way
      that xargs builds its command lines.  Only one instance of  `{}'
      is  allowed  within the command.  The command is executed in the
      starting directory.

Inwokacje będą znacznie mniejsze, ale nie gwarantuje się, że będą jednym. To, co powinieneś zrobić, to odczytać nazwy plików rozdzielone przez NUL ze skryptu ze standardowego wejścia, możliwe na podstawie argumentu wiersza poleceń -o -. Zrobiłbym coś takiego:

$ find . -name something.txt -print0 | myscript -0 -o -

i odpowiednio zaimplementuj argumenty opcji myscript.


Tak, system operacyjny nakłada ograniczenie liczby / wielkości argumentów, które można przekazać. W nowoczesnych systemach Linux jest to (gigantyczne) ( linux.die.net/man/2/execve ) (1/4 wielkości stosu, argumenty 0x7FFFFFFF). Sam bash AFAIK nie narzuca żadnych ograniczeń. Moje listy są znacznie mniejsze, a mój problem był spowodowany nieporozumieniem lub niepamięć, jak to xargsdziała. Twoje rozwiązanie jest rzeczywiście najbardziej niezawodne, ale w tym przypadku jest nadmierne.
Alexis

0

Czy nie ma sposobu na ochronę przestrzeni w rozszerzeniu wstecznym (lub $ (...))?

Nie, nie ma. Dlaczego?

Bash nie ma pojęcia, co należy chronić, a co nie.

W pliku / potoku unix nie ma tablic. To tylko strumień bajtów. Polecenie wewnątrz ``lub $()wyprowadza strumień, który bash połyka i traktuje jak pojedynczy ciąg. W tym momencie masz tylko dwie możliwości: umieść go w cudzysłowie, aby zachować go jako jeden ciąg lub umieść go nagiego, aby bash podzielił go zgodnie ze skonfigurowanym zachowaniem.

Więc jeśli chcesz, aby tablica była zdefiniowana, musisz zdefiniować format bajtów, który ma tablicę, a takie narzędzia lubią xargsi findrobią: jeśli uruchomisz je z -0argumentem, będą działać zgodnie z formatem tablicy binarnej, który kończy elementy za pomocą bajt zerowy, dodając semantykę do inaczej nieprzezroczystego strumienia bajtów.

Niestety bashnie można skonfigurować podziału ciągów w bajcie zerowym. Dzięki /unix//a/110108/17980 za pokazanie nam, że zshmoże.

xargs

Chcesz, aby polecenie wykonało się raz, i powiedziałeś, że to xargs -0 -n 10000rozwiązuje twój problem. Nie zapewnia, że ​​jeśli masz więcej niż 10000 parametrów, twoje polecenie uruchomi się więcej niż raz.

Jeśli chcesz, aby był ściśle uruchamiany raz lub nie powiódł się, musisz podać -xargument i -nargument większy niż -sargument (naprawdę: wystarczająco duży, aby cała wiązka argumentów o zerowej długości plus nazwa polecenia nie pasowały -swielkości). ( man xargs , patrz fragment daleko poniżej)

System, na którym aktualnie pracuję, ma ograniczony stos do około 8 milionów, więc oto mój limit:

$ printf '%s\0' -- {1..1302582} | xargs -x0n 2076858 -s 2076858 /bin/true
xargs: argument list too long
$ printf '%s\0' -- {1..1302581} | xargs -x0n 2076858 -s 2076858 /bin/true
(no output)

grzmotnąć

Jeśli nie chcesz włączać zewnętrznego polecenia, pętla podczas odczytu zasilająca tablicę, jak pokazano na /unix//a/110108/17980 , jest jedynym sposobem na podzielenie rzeczy przez bash bajt zerowy.

Pomysł na źródło skryptu, ( . ... "$@" )aby uniknąć limitu wielkości stosu, jest fajny (próbowałem, działa!), Ale prawdopodobnie nie jest ważny w normalnych sytuacjach.

Użycie specjalnego fd dla potoku procesu jest ważne, jeśli chcesz przeczytać coś innego ze standardowego wejścia, ale w przeciwnym razie nie będziesz go potrzebował.

Zatem najprostszy „rodzimy” sposób na codzienne potrzeby gospodarstwa domowego:

files=()
while IFS= read -rd '' file; do
    files+=("$file")
done <(find ... -print0)

myscriptornonscript "${files[@]}"

Jeśli podoba ci się drzewo procesu czyste i ładne, ta metoda pozwala to zrobić exec mynonscript "${files[@]}", co usuwa proces bash z pamięci, zastępując go wywoływanym poleceniem. xargszawsze pozostanie w pamięci podczas działania wywoływanej komendy, nawet jeśli komenda uruchomi się tylko raz.


To, co przemawia przeciwko natywnej metodzie bash, to:

$ time { printf '%s\0' -- {1..1302581} | xargs -x0n 2076858 -s 2076858 /bin/true; }

real    0m2.014s
user    0m2.008s
sys     0m0.172s

$ time {
  args=()
  while IFS= read -rd '' arg; do
    args+=( "$arg" )
  done < <(printf '%s\0' -- $(echo {1..1302581}))
  /bin/true "${args[@]}"
}
bash: /bin/true: Argument list too long

real    107m51.876s
user    107m38.532s
sys     0m7.940s

bash nie jest zoptymalizowany do obsługi tablicy.


człowiek xargs :

-n maks. argumenty

Użyj maksymalnie argumentów max-args w wierszu poleceń. W przypadku przekroczenia rozmiaru (patrz opcja -s) zostanie użytych mniej niż argumentów max-args, chyba że podano opcję -x, w którym to przypadku xargs zakończy działanie.

-s maks. znaki

Należy użyć maksymalnie znaków o maksymalnej liczbie znaków w wierszu polecenia, w tym argumentu polecenia i początkowych argumentów oraz kończących wartości zerowych na końcach ciągów argumentów. Największa dozwolona wartość zależy od systemu i jest obliczana jako limit długości argumentu dla exec, pomniejszony o wielkość środowiska, mniej 2048 bajtów nadmiaru. Jeśli ta wartość jest większa niż 128 kB, jako wartość domyślną stosuje się 128 kib; w przeciwnym razie wartością domyślną jest maksimum. 1 kB ma 1024 bajty.

-x

Wyjdź, jeśli rozmiar (patrz opcja -s) zostanie przekroczony.


Dzięki za wszystkie problemy, ale podstawowa przesłanka ignoruje fakt, że bash zwykle używa skomplikowanego systemu przetwarzania ofert. Ale nie w rozwinięciu backquote. Porównaj następujące (błędy, które dadzą, ale pokazać różnicę): ls "what is this"vs ls `echo '"what is this"'` . Ktoś zaniedbał wdrożenie przetwarzania cytatów w wyniku cudzysłowów.
Alexis

Cieszę się, że cudzysłowy nie przetwarzają ofert. Fakt, że nawet dzielą słowa, spowodował wystarczająco niejasny wygląd, drapanie głowy i wady bezpieczeństwa we współczesnej historii komputerów.
clacke

Pytanie brzmi: „Czy nie ma sposobu na ochronę przestrzeni w $(...)rozszerzeniu wstecznym (lub )?”, Więc wydaje się właściwe ignorowanie przetwarzania, które nie jest wykonywane w tej sytuacji.
clacke

Format tablicy elementów zakończonej znakiem null jest najprostszym, a zatem najbezpieczniejszym sposobem wyrażenia tablicy. Szkoda, że bashnie obsługuje tego natywnie, jak się wydaje zsh.
clacke

Właściwie to w tym tygodniu użyłem printf "%s\0"i xargs -0przeszedłem dookoła sytuacji cytowania, w której narzędzie pośrednie przepuszczałoby parametry przez ciąg analizowany przez powłokę. Cytowanie zawsze wraca, by cię ugryźć.
clacke
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.