find: -exec vs xargs (alias Dlaczego „find | xargs basename” psuje się?)

10

Próbowałem znaleźć wszystkie pliki określonego typu rozproszone w podkatalogach i do moich celów potrzebowałem tylko nazwy pliku. Próbowałem usunąć komponent ścieżki przez basename, ale to nie działało z xargs:

$ find . -name '*.deb' -print | xargs basename 
basename: extra operand `./pool/main/a/aalib/libaa1_1.4p5-37+b1_i386.deb'
Try `basename --help' for more information.

Otrzymuję to samo (dokładnie ten sam błąd) z jedną z tych odmian:

$ find . -name '*.deb' -print0 | xargs -0 basename 
$ find . -name '*.deb' -print | xargs basename {}

To z kolei działa zgodnie z oczekiwaniami:

$ find . -name '*.deb' -exec basename {} \;
foo
bar
baz

Dzieje się tak w aktualnych wersjach Cygwin i Debian 5.0.3. Moja diagnoza jest taka, że ​​xargs z jakiegoś powodu przekazuje dwie linie wejściowe do basename, ale dlaczego? Co tu się dzieje?

quack quixote
źródło

Odpowiedzi:

23

Ponieważ basenamechce tylko jednego parametru ... nie WIELE. I xargstworzy wiele parametrów.

Aby rozwiązać swój prawdziwy problem (wymień tylko nazwy plików):

 find . -name '*.deb' -printf "%f\n"

Który drukuje tylko „basename” (man find):

 %f     File's name with any leading directories
        removed (only the last element).
akira
źródło
1
oooh .... / znów uderza mnie w czoło / myślę, że potrzebuję książki „znajdź dla manekinów” ...
quack quixote
Myślałem, że chodzi o xargsto, że tworzy listę argumentów i przekazuje każdy do następnego polecenia? w przeciwnym razie jaka jest różnica między tym a find . -name '*.deb' | basename?
WindowsMaker
GNU basename ma teraz -aopcję: „obsługuje wiele argumentów i traktuje każdy z nich jak nazwę”.
biskup
1
@WindowsMaker xargskonwertuje stdinna argumenty poleceń. W pewnym sensie jest to przeciwieństwo echo, na które konwertuje swoje argumenty stdout. Różnica pomiędzy find ... | xargs -n1 basenamealbo find ... | xargs basename -ai find ... | basenameto, że były dwa będą pracować z wdrożeń basename, które ignorują stdin.
8bittree,
19

Spróbuj tego:

find . -name '*.deb' | xargs -n1 basename
perlguy9
źródło
to nie jest wyjaśnienie, to obejście. obejście to jest tak dobre, że wystarczy wywołać „basename” przez -exec dla każdego znalezionego pliku.
akira
4
+1 ... choć nie jest wyjaśnieniem, to skłoniłoby mnie do zbadania pokazanego przełącznika xargs, co ostatecznie doprowadziłoby mnie do ruchu uderzania w czoło, którego właśnie użyłem, czytając odpowiedzi Akiry i Johna T ...
quack quoteote
1
Tak to robię. Nie mam ochoty uczyć się wszystkich tajników findpolecenia, więc używam go tylko do wyszukiwania i wyświetlania plików, a do innych rzeczy używam xargs.
Ryan C. Thompson,
4

basename akceptuje tylko jeden argument. Używanie -execdziała poprawnie, ponieważ każdy z nich {}jest zastępowany przetwarzaną bieżącą nazwą pliku, a polecenie jest uruchamiane raz dla dopasowanego pliku , zamiast próbować wysłać wszystkie argumenty do nazwy basename za jednym razem.

John T.
źródło
3

xargs może zostać zmuszony do przekazania tylko jednego argumentu ...

find . -name '*.deb' -print | xargs -n1 basename

Działa to, jednak przyjęta odpowiedź jest używana findw bardziej odpowiedni sposób. Znalazłem to pytanie, szukając xargs basenameproblemów, ponieważ używam innego polecenia, aby uzyskać listę lokalizacji plików. -n1Flaga dla xargsbył ostateczną odpowiedzią dla mnie.

Flet
źródło