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ąć.

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.


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).


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

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

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

-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 /

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”
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.