Czy wykonać polecenie raz na linię wejściową potoku?

162

Chcę raz uruchomić polecenie Java dla każdego dopasowania ls | grep pattern -. W tym przypadku myślę, że mógłbym to zrobić, find pattern -exec java MyProg '{}' \;ale ciekawi mnie ogólny przypadek - czy można łatwo powiedzieć „uruchom polecenie raz dla każdego wiersza standardowego wejścia”? (W rybach lub bash.)

Xodarap
źródło

Odpowiedzi:

91

Właśnie to xargsrobi.

... | xargs command
Keith
źródło
25
Nie do końca. printf "foo bar\nbaz bat" | xargs echo wheeustąpi whee foo bar baz bat. Może dodać opcje -Llub -n?
Jander
3
@Jander Pytanie było raczej ogólne, więc podałem ogólne narzędzie. To prawda, że ​​będziesz musiał dostosować jego zachowanie za pomocą opcji w zależności od konkretnych okoliczności.
Keith
4
... | tr '\ n' '\ 0' | xargs -0
vrdhn
7
jak „szczególne okoliczności, które dają właściwą odpowiedź na pytanie”. :)
mattdm
7
Jeśli chcesz zobaczyć, jak to zrobić za pomocą xargs, zobacz moją odpowiedź poniżej.
Michael Goldshteyn
167

Przyjęte rozwiązanie ma rację, ale kluczowe jest, aby przejść xargsdo -n1przełącznika, co oznacza „Wykonaj polecenie po każdym wierszu wyjścia”

cat file... | xargs -n1 command

Lub, dla jednego pliku wejściowego, możesz catcałkowicie uniknąć potoku i po prostu przejść z:

<file xargs -n1 command
Michael Goldshteyn
źródło
1
Interesująca jest również zdolność xargsdo nie działać, jeśli stdinjest pusta: --no-run-if-empty -r: Jeżeli standardowe wejście nie zawiera żadnych nonblanks, nie działają komendy. Zwykle polecenie jest uruchamiane raz, nawet jeśli nie ma danych wejściowych. Ta opcja jest rozszerzeniem GNU.
Ronan Jouchet
4
Jak uzyskać dostęp do linii w środku command?
BT
To jest prawidłowe użycie xargs. Bez opcji -n1 działa tylko w przypadku poleceń, które traktują listy parametrów jako wielokrotne wywołania, które nie wszystkie.
masterxilo,
3
printf "foo bar \ nbaz bat" | xargs -n1 echo whee dzieli się na słowa, a nie na linie
Gismo Ranas
112

W Bash lub innej powłoce w stylu Bourne'a (jesion, ksh, zsh,…):

while read -r line; do command "$line"; done

read -rczyta pojedynczy wiersz ze standardowego wejścia ( readbez -rinterpretacji ukośników odwrotnych, nie chcesz tego). W ten sposób możesz wykonać jedną z następujących czynności:

$ command | while read -r line; do command "$line"; done  

$ while read -r line; do command "$line"; done <file
Steven D.
źródło
6
Kiedy próbowałem tail -f syslog | grep -e something -e somethingelse| while read line; do echo $line; done, nie działało. Działało z plikiem podłączonym do whilepętli, działało z samym tail -f, działało z tylko grep, ale nie z obiema rurami. Dając grepsię --line-bufferedopcję wykonane pracować
Działa to także wtedy, gdy każda linia musi zostać wysłany do stdin:command | while read -r line; do echo "$line" | command ; done
Den
21

Zgadzam się z Keith, xargs jest najbardziej ogólnym narzędziem do tego zadania.

Zwykle stosuję podejście 3-etapowe.

  • rób podstawowe rzeczy, aż będziesz mieć coś, z czym chciałbyś pracować
  • przygotuj linię z awk, aby uzyskać poprawną składnię
  • następnie niech xargs go wykona, być może przy pomocy bash.

Istnieją mniejsze i szybsze sposoby, ale te sposoby prawie zawsze działają.

Prosty przykład:

ls | 
grep xls | 
awk '{print "MyJavaProg --arg1 42 --arg2 "$1"\0"}' | 
xargs -0 bash -c

2 pierwsze linie wybierają niektóre pliki do pracy, a następnie awk przygotowuje ładny ciąg z poleceniem do wykonania i pewnymi argumentami, a $ 1 to pierwsza kolumna wejściowa z potoku. I wreszcie upewniam się, że xargs wysyła ten ciąg do bash, który po prostu go wykonuje.

To trochę przesada, ale ten przepis pomógł mi w wielu miejscach, ponieważ jest bardzo elastyczny.

Johan
źródło
6
Uwaga: xargs -0używa bajtu zerowego jako separatora rekordów, więc twoja instrukcja drukowania awk powinna byćprintf("MyJavaProg --args \"%s\"\0",$1)
glenn jackman
@glenn: Brakowało znaku null, zaktualizuje odpowiedź
Johan
@Johan, nie jest to wielka sprawa, ale jeśli używasz awk, możesz zrobić to dopasowując wzór i pominąć grep np.ls | awk '/xls/ {print...
Eric Renouf
15

GNU Parallel jest przeznaczony do tego rodzaju zadań. Najprostsze użycie to:

cat stuff | grep pattern | parallel java MyProg

Obejrzyj film wprowadzający, aby dowiedzieć się więcej: http://www.youtube.com/watch?v=OpaiGYxkSuQ

Ole Tange
źródło
1
Nie ma cattu potrzeby , ponieważ grepmożna bezpośrednio odczytać plik
Eric Renouf
1
Dzięki za link, niekoniecznie zgadzam się, że jest łatwiejszy do odczytania, ale miło wiedzieć, że został rozważony niezależnie od tego. Chciałbym tylko teraz nieco sprzeciwić się, że link tak naprawdę nie ma tutaj zastosowania, ponieważ alternatywa nie jest tak naprawdę, < stuff grep patternale grep pattern stuffnie wymaga przekierowania ani kota. Mimo to nie zmienia to w istotny sposób twojego argumentu, a jeśli uważasz, że łatwiej jest zawsze używać rzeczy w rurze, która zaczyna się od cat, a potem moc dla ciebie
Eric Renouf
8

Ponadto, while readwłóż skorupkę ryby (zakładam, że chcesz skorupkę ryby, biorąc pod uwagę, że użyłeś etykiety ).

command | while read line
    command $line
end

Kilka punktów do odnotowania.

  • readnie bierze -rargumentów i nie interpretuje ukośników odwrotnych, aby ułatwić najczęstsze przypadki użycia.
  • Nie musisz cytować $line, ponieważ w przeciwieństwie do bash, ryby nie oddzielają zmiennych spacjami.
  • commandsam w sobie jest błędem składniowym (aby złapać takie użycie argumentów zastępczych). Zamień go na prawdziwe polecenie.
Konrad Borowski
źródło
Nie whiletrzeba go łączyć z do& donezamiast end?
aff
@aff Dotyczy to przede wszystkim skorupy ryb, która ma inną składnię.
Konrad Borowski
Ach, więc to właśnie oznacza ryba.
aff
6

Jeśli chcesz kontrolować, gdzie dokładnie argument wejściowy jest wstawiany do wiersza poleceń lub jeśli chcesz go powtórzyć kilka razy, musisz użyć xargs -I{}.

PRZYKŁAD 1

Utwórz pustą strukturę folderów, another_folderktóra odzwierciedla podfoldery w bieżącym katalogu:

    ls -1d ./*/ | xargs -I{} mkdir another_folder/{}
PRZYKŁAD 2

Zastosuj operację na liście plików pochodzących ze standardowego wejścia, w tym przypadku wykonaj kopię każdego .htmlpliku, dołączając .bakrozszerzenie:

    find . -iname "*.html" | xargs -I{} cp {} {}.bak

Ze strony podręcznika xargsdla MacOS / BSD :

 -I replstr
         Execute utility for each input line, replacing one or more occurrences of
         replstr in up to replacements (or 5 if no -R flag is specified) arguments
         to utility with the entire line of input.  The resulting arguments, after
         replacement is done, will not be allowed to grow beyond 255 bytes; this is
         implemented by concatenating as much of the argument containing replstr as
         possible, to the constructed arguments to utility, up to 255 bytes.  The
         255 byte limit does not apply to arguments to utility which do not contain
         replstr, and furthermore, no replacement will be done on utility itself.
         Implies -x.

Linux xargsstrona man :

   -I replace-str
          Replace  occurrences of replace-str in the initial-
          arguments with names read from standard input.  Al
          so,  unquoted  blanks do not terminate input items;
          instead the separator  is  the  newline  character.
          Implies -x and -L 1.
ccpizza
źródło
1

Kiedy mam do czynienia z potencjalnie niezaangażowanymi danymi wejściowymi, lubię widzieć, jak całe zadanie „pisze się” wiersz po wierszu do kontroli wizualnej, zanim go uruchomię (szczególnie, gdy jest to coś destrukcyjnego, jak czyszczenie skrzynek pocztowych ludzi).

Więc to, co robię, to wygenerowanie listy parametrów (tj. Nazw użytkowników), przesłanie jej do pliku w sposób jeden rekord na linię, jak poniżej:

johndoe  
jamessmith  
janebrown  

Następnie otwieram listę vimi zmieniam ją za pomocą wyszukiwania i zamieniam wyrażenia, aż otrzymam listę pełnych poleceń, które należy wykonać, w następujący sposób:

/bin/rm -fr /home/johndoe  
/bin/rm -fr /home/jamessmith 

W ten sposób, jeśli wyrażenie regularne jest niekompletne, zobaczysz, w jakim poleceniu wystąpią potencjalne problemy (tj. /bin/rm -fr johnnyo connor). W ten sposób możesz cofnąć wyrażenie regularne i spróbować ponownie, używając bardziej niezawodnej wersji. Zmiana nazwy jest znana z tego powodu, ponieważ ciężko jest zająć się wszystkimi przypadkowymi przypadkami, takimi jak Van Gogh, O'Connors, St. Clair, Smith-Wesson.

Mając set hlsearchjest przydatna w ten sposób vim, jak to podkreślają wszystkie mecze, więc można łatwo dostrzec, jeśli nie pasuje, lub mecze w niezamierzony sposób.

Gdy wyrażenie regularne jest idealne i obejmuje wszystkie przypadki, w których można przetestować / wymyślić, zwykle przekształcam je w wyrażenie sed, aby można je było w pełni zautomatyzować do kolejnego uruchomienia.

W przypadkach, w których liczba wierszy danych wejściowych uniemożliwia sprawdzenie wizualne, zdecydowanie polecam powtórzenie polecenia na ekranie (lub jeszcze lepiej w dzienniku) przed jego wykonaniem, więc jeśli wystąpi błąd, dokładnie wiesz, które polecenie spowodowało zawieść. Następnie możesz wrócić do pierwotnego wyrażenia regularnego i dostosować go jeszcze raz.

Marcin
źródło
0

Jeśli program ignoruje potok, ale akceptuje pliki jako argumenty, możesz po prostu wskazać specjalny plik /dev/stdin.

Nie znam java, ale oto przykład, jak zrobiłbyś to dla bash:

$ echo $'pwd \n cd / \n pwd' |bash /dev/stdin
/home/rolf
/

$ Jest niezbędny, aby bash mógł zostać przetłumaczony \nna nowe linie. Nie jestem pewien dlaczego.

Rolf
źródło
0

Wolę to - pozwalając na wieloliniowe polecenia i czysty kod

find -type f -name filenam-pattern* | while read -r F
do
  echo $F
  cat $F | grep 'some text'
done

ref https://stackoverflow.com/a/3891678/248616

Nam G VU
źródło