Niech xargs wykona polecenie raz dla każdego wiersza wprowadzania


341

Jak mogę zmusić xargs do wykonania polecenia dokładnie raz dla każdej podanej linii danych wejściowych? Domyślnie zachowuje się dzielenie linii i wykonywanie polecenia raz, przekazując wiele linii do każdej instancji.

Od http://en.wikipedia.org/wiki/Xargs :

find / path -type f -print0 | xargs -0 mb

W tym przykładzie find zawiera dane wejściowe xargs z długą listą nazw plików. xargs następnie dzieli tę listę na podlisty i wywołuje rm jeden raz dla każdej podlisty. Jest to bardziej wydajne niż ta funkcjonalnie równoważna wersja:

find / path -type f -exec rm '{}' \;

Wiem, że find ma flagę „exec”. Cytuję tylko przykładowy przykład z innego zasobu.


4
W podanym przez Ciebie przykładzie find /path -type f -deletebyłoby jeszcze bardziej wydajne :)
tzot

staraj się nie używać xargs ...
Naib

6
OP, wiem, że to pytanie jest bardzo stare, ale wciąż pojawia się w Google i IMHO zaakceptowana odpowiedź jest błędna. Zobacz moją dłuższą odpowiedź poniżej.
Tobia,

Zastanów się nad przejściem na odpowiedź @ Tobia, która jest znacznie lepsza. Akceptowana odpowiedź nie obsługuje spacji w nazwach i nie dopuszcza wielu argumentów w poleceniu xargs, które jest jedną z głównych cech xargs.
Gray

Odpowiedzi:


392

Poniższe będzie działać tylko wtedy, gdy nie masz spacji w danych wejściowych:

xargs -L 1
xargs --max-lines=1 # synonym for the -L option

ze strony podręcznika:

-L max-lines
          Use at most max-lines nonblank input lines per command line.
          Trailing blanks cause an input line to be logically continued  on
          the next input line.  Implies -x.

13
Dla mnie może się to wydarzyć, ponieważ xargs -n 1ten, który podałeś, pokazał „listę argumentów za długą”.
Wernight

19
Jeśli MAX-LINESzostanie pominięty, domyślnie xargs -lwynosi 1, więc wystarczy. Zobaczyć info xargs.
Thor

3
@Wernight: „-n1” nie daje 1 wywołania na linię wejściową. może twoja linia wejściowa była za długa. Demo: echo "foo bar" | xargs -n1 echo. dlatego jeśli wstawisz coś takiego jak „ls”, nie poradzi sobie dobrze ze spacjami.
gatoatigrado

8
To jest źle. -L 1nie odpowiada na pierwotne pytanie i -n 1czyni to tylko w jednej z możliwych interpretacji. Zobacz moją długą odpowiedź poniżej.
Tobia,

2
@Tobia: Odpowiada na oryginalne pytanie, które dotyczyło konkretnie linii wejściowych. Właśnie to -L 1robi. Wydawało mi się, że OP najwyraźniej stara się uniknąć domyślnego zachowania polegającego na dzieleniu na części, a ponieważ zostało to zaakceptowane, zakładam, że miałem rację. Twoja odpowiedź dotyczy nieco innego przypadku użycia, w którym również chcesz zachować zbrylanie.
Draemon

207

Wydaje mi się, że wszystkie istniejące odpowiedzi na tej stronie są błędne, w tym ta oznaczona jako poprawna. Wynika to z tego, że pytanie jest niejednoznacznie sformułowane.

Podsumowanie:   Jeśli chcesz wykonać polecenie „dokładnie raz dla każdej podanej linii wejściowej”, przekazując całą linię (bez nowej linii) do polecenia jako pojedynczy argument, jest to najlepszy sposób na to, aby to zrobić:

... | tr '\n' '\0' | xargs -0 -n1 ...

GNU xargsmoże, ale nie musi, mieć przydatne rozszerzenia, które pozwalają ci się pozbyć tr, ale nie są one dostępne w OS X i innych systemach UNIX.

Teraz długie wyjaśnienie…


Podczas korzystania z xargs należy wziąć pod uwagę dwa problemy:

  1. jak dzieli dane wejściowe na „argumenty”; i
  2. ile argumentów przekazać polecenie potomne naraz.

Aby przetestować zachowanie xargs, potrzebujemy narzędzia, które pokazuje, ile razy jest wykonywane i ile argumentów. Nie wiem, czy jest do tego standardowe narzędzie, ale dość łatwo możemy to napisać w bash:

#!/bin/bash
echo -n "-> "; for a in "$@"; do echo -n "\"$a\" "; done; echo

Zakładając, że zapiszesz go showw bieżącym katalogu i sprawisz, że będzie wykonywalny, oto jak to działa:

$ ./show one two 'three and four'
-> "one" "two" "three and four" 

Teraz, jeśli oryginalne pytanie naprawdę dotyczy punktu 2 powyżej (tak mi się wydaje, po kilkukrotnym przeczytaniu) i należy go przeczytać w ten sposób (zmiany pogrubioną czcionką):

Jak mogę zmusić xargs do wykonania polecenia dokładnie raz dla każdego podanego argumentu wejściowego? Jego domyślne zachowanie polega na dzieleniu danych wejściowych na argumenty i wykonywaniu polecenia tak mało, jak to możliwe , przekazując wiele argumentów do każdej instancji.

wtedy odpowiedź brzmi -n 1.

Porównajmy domyślne zachowanie xargs, które dzieli dane wejściowe wokół białych znaków i wywołuje polecenie tak mało razy, jak to możliwe:

$ echo one two 'three and four' | xargs ./show 
-> "one" "two" "three" "and" "four" 

i jego zachowanie z -n 1:

$ echo one two 'three and four' | xargs -n 1 ./show 
-> "one" 
-> "two" 
-> "three" 
-> "and" 
-> "four" 

Z drugiej strony, jeśli pierwotne pytanie dotyczyło dzielenia danych w punkcie 1. i powinno być czytane w ten sposób (wiele osób przybywających tutaj wydaje się myśleć, że tak jest, lub mylą te dwie kwestie):

Jak mogę zmusić xargs do wykonania polecenia z dokładnie jednym argumentem dla każdego wiersza danych wejściowych? Jego domyślne zachowanie polega na dzieleniu linii wokół białych znaków .

wtedy odpowiedź jest bardziej subtelna.

Można by pomyśleć, że -L 1może to być pomocne, ale okazuje się, że nie zmienia to analizy argumentów. Wykonuje polecenie tylko raz dla każdej linii wejściowej, z tyloma argumentami, ile było w tej linii wejściowej:

$ echo $'one\ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" 
-> "two" 
-> "three" "and" "four" 

Nie tylko to, ale jeśli linia kończy się spacją, jest ona dodawana do następnej:

$ echo $'one \ntwo\nthree and four' | xargs -L 1 ./show 
-> "one" "two" 
-> "three" "and" "four" 

Oczywiście -Lnie chodzi o zmianę sposobu, w jaki xargs dzieli dane wejściowe na argumenty.

Jedynym argumentem, który robi to w sposób wieloplatformowy (z wyłączeniem rozszerzeń GNU), jest -0podział danych wejściowych na bajty NUL.

Następnie jest tylko kwestia przetłumaczenia nowego wiersza na NUL za pomocą tr:

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 ./show 
-> "one " "two" "three and four" 

Teraz parsowanie argumentów wygląda dobrze, w tym końcowe białe znaki.

Wreszcie, jeśli połączysz tę technikę -n 1, uzyskasz dokładnie jedno wykonanie polecenia w wierszu wprowadzania, niezależnie od tego, co masz, co może być jeszcze innym sposobem spojrzenia na pierwotne pytanie (być może najbardziej intuicyjne, biorąc pod uwagę tytuł):

$ echo $'one \ntwo\nthree and four' | tr '\n' '\0' | xargs -0 -n1 ./show 
-> "one " 
-> "two" 
-> "three and four" 

wygląda na to, że to lepsza odpowiedź. jednak nadal nie do końca rozumiem, jaka jest różnica między -L i -n ... czy możesz wyjaśnić coś więcej?
olala

5
@olala -Lwykonuje polecenie raz na linię wejściową (ale spacja na końcu linii łączy ją z kolejną linią, a linia jest nadal podzielona na argumenty zgodnie z białymi znakami); podczas gdy -nwykonuje polecenie raz na argument wejściowy. Jeśli policzysz liczbę ->przykładów wyjściowych, będzie to liczba wykonań skryptu ./show.
Tobia,

Widzę! nie zdawałem sobie sprawy, że spacja na końcu linii łączy ją z następną linią. dzięki!
olala

4
GNU xargsmoże, ale nie musi, mieć przydatne rozszerzenia, które pozwalają ci się pozbyć.tr Ma takie bardzo przydatne rozszerzenie; from xargs --help- -d, --delimiter = CHARACTER elementy w strumieniu wejściowym są oddzielone CHARACTER, a nie spacjami; wyłącza przetwarzanie cytatów i ukośników odwrotnych oraz logiczne przetwarzanie EOF
Piotr Dobrogost

Ta odpowiedź wydaje się mylona -L. -Lnie mówi, ile razy wykonać skrypt w wierszu, mówi, ile wierszy danych wejściowych należy zużyć naraz.
Moberg

22

Jeśli chcesz uruchomić polecenie dla każdej linii (tzn. Wyniku) find, to po co ci to xargs?

Próbować:

find ścieżka -type f -exec twoje polecenie {} \;

gdzie literał {}zostaje zastąpiony nazwą pliku, a literał \;jest potrzebny, findaby wiedzieć, że niestandardowe polecenie się tam kończy.

EDYTOWAĆ:

(po edycji pytania wyjaśniając, że wiesz -exec)

Od man xargs:

-L maks. Wierszy
Użyj maksymalnie maks. Wierszy niepustych wierszy wejściowych na wiersz polecenia. Końcowe spacje powodują, że linia wejściowa jest logicznie kontynuowana w następnej linii wejściowej. Implikuje -x.

Pamiętaj, że nazwy plików kończące się spacjami spowodują problemy, jeśli użyjesz xargs:

$ mkdir /tmp/bax; cd /tmp/bax
$ touch a\  b c\  c
$ find . -type f -print | xargs -L1 wc -l
0 ./c
0 ./c
0 total
0 ./b
wc: ./a: No such file or directory

Więc jeśli nie zależy ci na tej -execopcji, lepiej użyj -print0i -0:

$ find . -type f -print0 | xargs -0L1 wc -l
0 ./c
0 ./c
0 ./b
0 ./a

17

Jak mogę zmusić xargs do wykonania polecenia dokładnie raz dla każdej podanej linii danych wejściowych?

-L 1jest prostym rozwiązaniem, ale nie działa, jeśli którykolwiek z plików zawiera spacje. Jest to kluczowa funkcja -print0argumentu find - aby oddzielić argumenty znakiem „\ 0” zamiast białych znaków. Oto przykład:

echo "file with space.txt" | xargs -L 1 ls
ls: file: No such file or directory
ls: with: No such file or directory
ls: space.txt: No such file or directory

Lepszym rozwiązaniem jest trkonwersja znaków nowej linii na \0znaki null ( ), a następnie użycie xargs -0argumentu. Oto przykład:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 ls
file with space.txt

Jeśli musisz ograniczyć liczbę wywołań, możesz użyć -n 1argumentu, aby wykonać jedno wywołanie do programu dla każdego wejścia:

echo "file with space.txt" | tr '\n' '\0' | xargs -0 -n 1 ls

Pozwala to również na filtrowanie danych wyjściowych find przed konwersją przerw w null.

find . -name \*.xml | grep -v /target/ | tr '\n' '\0' | xargs -0 tar -cf xml.tar

1
Wystąpił błąd składniowy w drugim bloku kodu tr '\ n' '\ 0 \ => tr' \ n '' \ 0 '. Próbowałem to naprawić, ale „Edycja musi mieć co najmniej 6 znaków” (wygląda na to, że głupi jak git odmawia popełnienia, ponieważ moja zmiana była mniejsza niż 6 znaków)
htaccess

1
Co to znaczy: „Kolejnym problemem związanym z używaniem -Ljest to, że nie pozwala na wiele argumentów dla każdego xargswywołania polecenia.”?
Moberg

Poprawiłem swoją odpowiedź, aby usunąć te obce informacje @Mergerg.
Gray

11

Kolejna alternatywa ...

find /path -type f | while read ln; do echo "processing $ln"; done

9

Te dwa sposoby również działają i będą działać dla innych poleceń, które nie używają find!

xargs -I '{}' rm '{}'
xargs -i rm '{}'

przykładowy przypadek użycia:

find . -name "*.pyc" | xargs -i rm '{}'

usunie wszystkie pliki pyc z tego katalogu, nawet jeśli pliki pyc zawierają spacje.


To powoduje jedno wywołanie narzędzia dla każdego elementu, który nie jest optymalny.
Szary

7
find path -type f | xargs -L1 command 

to wszystko czego potrzebujesz.


4

Następujące polecenie znajdzie wszystkie pliki (-typ f), /patha następnie skopiuje je za pomocą cpdo bieżącego folderu. Zwróć uwagę na użycie if, -I %aby określić symbol zastępczy w cpwierszu poleceń, aby argumenty mogły być umieszczone po nazwie pliku.

find /path -type f -print0 | xargs -0 -I % cp % .

Testowane z xargs (GNU findutils) 4.4.0


2

Możesz ograniczyć liczbę wierszy lub argumentów (jeśli między argumentami są spacje), używając odpowiednio flag --max-lines lub --max-args.

  -L max-lines
         Use at most max-lines nonblank input lines per command line.  Trailing blanks cause an input line to be logically continued on the next  input
         line.  Implies -x.

  --max-lines[=max-lines], -l[max-lines]
         Synonym  for  the -L option.  Unlike -L, the max-lines argument is optional.  If max-args is not specified, it defaults to one.  The -l option
         is deprecated since the POSIX standard specifies -L instead.

  --max-args=max-args, -n max-args
         Use at most max-args arguments per command line.  Fewer than max-args arguments will be used if the size (see  the  -s  option)  is  exceeded,
         unless the -x option is given, in which case xargs will exit.

0

Wygląda na to, że nie mam wystarczającej reputacji, aby dodać komentarz do powyższej odpowiedzi Tobii , więc dodam tę „odpowiedź”, aby pomóc tym z nas, którzy chcą eksperymentować w xargsten sam sposób na platformach Windows.

Oto plik wsadowy systemu Windows, który robi to samo, co szybko zakodowany skrypt Tobia „show”:

@echo off
REM
REM  cool trick of using "set" to echo without new line
REM  (from:  http://www.psteiner.com/2012/05/windows-batch-echo-without-new-line.html)
REM
if "%~1" == "" (
    exit /b
)

<nul set /p=Args:  "%~1"
shift

:start
if not "%~1" == "" (
    <nul set /p=, "%~1"
    shift
    goto start
)
echo.

0

Odpowiedzi @Draemon wydają się być poprawne z „-0” nawet ze spacją w pliku.

Próbowałem polecenia xargs i okazało się, że „-0” działa idealnie z „-L”. nawet spacje są traktowane (jeśli wejście zostało zakończone zerem). Oto przykład:

#touch "file with space"
#touch "file1"
#touch "file2"

Poniższe polecenie podzieli wartości null i wykona polecenie dla każdego argumentu na liście:

 #find . -name 'file*' -print0 | xargs -0 -L1
./file with space
./file1
./file2

więc -L1wykona argument na każdym znaku zakończonym znakiem null, jeśli zostanie użyty z „-0”. Aby zobaczyć różnicę, spróbuj:

 #find . -name 'file*' -print0 | xargs -0 | xargs -L1
 ./file with space ./file1 ./file2

nawet to wykona się raz:

 #find . -name 'file*' -print0  | xargs -0  | xargs -0 -L1
./file with space ./file1 ./file2

Polecenie zostanie wykonane raz, ponieważ „-L” nie dzieli się teraz na bajt zerowy. do działania musisz podać zarówno „-0”, jak i „-L”.


-3

W twoim przykładzie, przesyłanie danych wyjściowych polecenia find do xargs polega na tym, że standardowym zachowaniem opcji find -exec jest wykonanie polecenia raz dla każdego znalezionego pliku. Jeśli używasz find i chcesz jego standardowe zachowanie, odpowiedź jest prosta - nie używaj xargs na początek.


Właściwie to, co mogę sugerować po modyfikacjach OP, to to, że dane wejściowe nie mają nic wspólnego find, i dlatego nie preferują tej -execopcji.
tzot

-3

Wykonaj ant clean-all na każdym pliku build.xml w bieżącym lub podfolderze.

find . -name 'build.xml' -exec ant -f {} clean-all \;

Nie każdy ma antzainstalowany.
Szary
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.