Jak przekazać każdą linię pliku tekstowego jako argument do polecenia?

42

Chcę napisać skrypt, który przyjmuje .txtnazwę pliku jako argument, odczytuje plik linia po linii i przekazuje każdą linię do polecenia. Na przykład uruchamia się command --option "LINE 1", command --option "LINE 2"itd. Dane wyjściowe polecenia są zapisywane w innym pliku. Jak mam to zrobić? Nie wiem od czego zacząć.

WojciechF
źródło

Odpowiedzi:

26

Użyj while readpętli:

: > another_file  ## Truncate file.

while IFS= read -r LINE; do
    command --option "$LINE" >> another_file
done < file

Innym jest przekierowanie wyjścia według bloku:

while IFS= read -r LINE; do
    command --option "$LINE"
done < file > another_file

Ostatnim jest otwarcie pliku:

exec 4> another_file

while IFS= read -r LINE; do
    command --option "$LINE" >&4
    echo xyz  ## Another optional command that sends output to stdout.
done < file

Jeśli jedno z poleceń czyta wejście, byłby to dobry pomysł, aby użyć innego fd dla wejścia więc polecenia nie zje go (tutaj zakładając ksh, zshalbo bashza -u 3, użyj <&3zamiast przenośnie):

while IFS= read -ru 3 LINE; do
    ...
done 3< file

Wreszcie, aby zaakceptować argumenty, możesz:

#!/bin/bash

FILE=$1
ANOTHER_FILE=$2

exec 4> "$ANOTHER_FILE"

while IFS= read -ru 3 LINE; do
    command --option "$LINE" >&4
done 3< "$FILE"

Który mógłby działać jako:

bash script.sh file another_file

Dodatkowy pomysł. Za bashpomocą readarray:

readarray -t LINES < "$FILE"

for LINE in "${LINES[@]}"; do
    ...
done

Uwaga: IFS=można pominąć, jeśli nie przeszkadza ci przycinanie wartości linii spacji wiodących i końcowych.

konsolebox
źródło
28

Inną opcją jest xargs.

Z GNU xargs:

< file xargs -I{} -d'\n' command --option {} other args

{} to symbol zastępczy wiersza tekstu.

Inne xargsnie mają -d, ale niektóre mają -0dane rozdzielane przez NUL. Dzięki nim możesz:

< file tr '\n' '\0' | xargs -0 -I{} command --option {} other args

W systemach zgodnych z Unix ( -Ijest opcjonalny w POSIX i wymagany tylko w systemach zgodnych z Unix), musisz wstępnie przetworzyć dane wejściowe, aby zacytować wiersze w formacie oczekiwanym przez xargs:

< file sed 's/"/"\\""/g;s/.*/"&"/' |
  xargs -E '' -I{} command --option {} other args

Należy jednak pamiętać, że niektóre xargsimplementacje mają bardzo niski limit maksymalnego rozmiaru argumentu (na przykład 255 w systemie Solaris, minimum dozwolone przez specyfikację Unix).

ctrl-alt-delor
źródło
redukowalny do<file xargs -L 1 -I{} command --option {} other args
iruvar
@iruvar, nie, to przetworzy cytaty i usunie puste miejsca.
Stéphane Chazelas
7

Trzymając się dokładnie pytania:

#!/bin/bash

# xargs -n param sets how many lines from the input to send to the command

# Call command once per line
[[ -f $1 ]] && cat $1 | xargs -n1 command --option

# Call command with 2 lines as args, such as an openvpn password file
# [[ -f $1 ]] && cat $1 | xargs -n2 command --option

# Call command with all lines as args
# [[ -f $1 ]] && cat $1 | xargs command --option
miigotu
źródło
1
działało jak urok :-)
BugHunter
3

Najlepsza odpowiedź, jaką znalazłem, to:

for i in `cat`; do "$cmd" "$i"; done < $file

EDYTOWAĆ:

... cztery lata później ...

po kilku głosach negatywnych i kilku doświadczeniach poleciłbym teraz następujące

xargs -l COMMAND < file
Steffen Roller
źródło
3
Spowoduje to wywołanie polecenia raz dla każdego słowa w pliku. Powinieneś także zawsze cytować odniesienia do zmiennych powłoki (jak w do "$cmd" "$i";), chyba że masz powód, aby tego nie robić; jeśli plik *sam w sobie zawiera słowo, uruchomiłby się kod $cmd *, co oczywiście uruchomiłoby polecenie z listą plików w bieżącym katalogu.
G-Man mówi „Reinstate Monica”
1
@ G-Man, poza zshtym, `cat`że już rozwinąłby *(niekwotowany $imógłby nadal rozwinąć niektóre symbole wieloznaczne (druga runda), jeśli rozszerzenie `cat`wprowadzi niektóre). W każdym razie takie podejście jest rzeczywiście błędne.
Stéphane Chazelas
1
+1. Działa to dobrze, jeśli każdy wiersz zawiera tylko dane potrzebne do użycia polecenia bez spacji.
Subin Sebastian,
Działa to na każde słowo, czy istnieje wariant tego, który działa na każdą linię?
thebunnyrules
Pytanie polegało na przetworzeniu listy nazw plików linia po linii.
Steffen Roller
2
    sed "s/'/'\\\\''/g;s/.*/\$* '&'/" <<\FILE |\
    sh -s -- command echo --option
all of the{&}se li$n\es 'are safely shell
quoted and handed to command as its last argument
following --option, and, here, before that echo
FILE

WYNIK

--option all of the{&}se li$n\es 'are safely shell
--option quoted and handed to command as its last argument
--option following --option, and, here, before that echo
mikeserv
źródło
-2
ed file.txt
%g/^/s// /
2,$g/^/-,.j
1s/^/command/
wq
chmod 755 file.txt
./file.txt

Weź wszystkie wiersze pliku i przekaż je jako argumenty do pojedynczego polecenia, tj.

command line1 line2 line3 ....

Jeśli potrzebujesz --optionflagi, aby poprzedzać każdą linię, zmień drugie polecenie na:

%g/^/s// --option /
użytkownik2217522
źródło
1
Co -1 za używanie ed ... naprawdę?
user2217522,
3
Nie głosowałem za tobą, ale osoba, która prawdopodobnie miała następujące powody: (1) To nie robi tego, o co pyta pytanie. To wywołuje polecenie raz z całą zawartością pliku w linii poleceń; zamiast raz na linię . (2) Nie działa to na znaki specjalne dla powłoki (które mogą znajdować się w pliku); na przykład ', ", <, >, ;, itd. (3) tworzy niepotrzebny plik tymczasowy. (4) Takie rzeczy zwykle wykonuje się za pomocą „tutaj dokumentów”. (5) Twoje edpolecenia są niezdarne; pierwsze dwa polecenia można zredukować do %s/^/ /i %j.
G-Man mówi „Reinstate Monica”