Jak używać wierszy pliku jako argumentów polecenia?

158

Powiedz, mam plik foo.txtokreślający Nargumenty

arg1
arg2
...
argN

które muszę przekazać do polecenia my_command

Jak używać wierszy pliku jako argumentów polecenia?

Yoo
źródło
10
„argumenty”, liczba mnoga czy „argument”, pojedynczy? Zaakceptowana odpowiedź jest prawidłowa tylko w przypadku jednoargumentowym - o co pyta tekst główny - ale nie w przypadku wieloargumentowym, o który wydaje się pytać temat.
Charles Duffy,
4 poniższe odpowiedzi zwykle dają identyczne wyniki, ale mają nieco inną semantykę. Teraz pamiętam, dlaczego przestałem pisać skrypty basha: P
Navin

Odpowiedzi:

236

Jeśli twoja powłoka to bash (między innymi), skrót do $(cat afile)to $(< afile), więc napiszesz:

mycommand "$(< file.txt)"

Udokumentowane na stronie podręcznika bash w sekcji „Zastępowanie poleceń”.

Alternatywnie, poproś o odczytanie polecenia ze stdin, więc: mycommand < file.txt

glenn jackman
źródło
11
Mówiąc pedantycznie, nie jest to skrót do korzystania z <operatora. Oznacza to, że sama powłoka wykona przekierowanie, a nie wykona catplik binarny ze względu na swoje właściwości przekierowania.
lstyls
2
Jest to ustawienie jądra, więc musisz ponownie skompilować jądro. Albo zbadaj polecenie xargs
glenn jackman
3
@ykaner, ponieważ bez "$(…)"tego zawartość file.txtbyłaby przekazywana na standardowe wejście programu mycommand, a nie jako argument. "$(…)"oznacza uruchomienie polecenia i zwrócenie wyniku w postaci łańcucha ; tutaj „polecenie” czyta tylko plik, ale może być bardziej złożone.
törzsmókus
3
To nie działa dla mnie, chyba że zgubię cytaty. W cudzysłowie przyjmuje cały plik jako 1 argument. Bez cudzysłowów interpretuje każdy wiersz jako oddzielny argument.
Pete
1
@LarsH, masz całkowitą rację. To mniej zagmatwane sformułowanie, dzięki.
lstyls
37

Jak już wspomniano, możesz użyć lewych apostrofów lub $(cat filename).

To, o czym nie wspomniano, i myślę, że jest to ważne, to fakt, że musisz pamiętać, że powłoka rozbije zawartość tego pliku według białych znaków, podając każde znalezione słowo jako argument do polecenia. I chociaż możesz być w stanie ująć argument wiersza poleceń w cudzysłów, tak aby mógł zawierać spacje, sekwencje specjalne itp., Czytanie z pliku nie da tego samego. Na przykład, jeśli twój plik zawiera:

a "b c" d

argumenty, które otrzymasz, to:

a
"b
c"
d

Jeśli chcesz pobrać każdy wiersz jako argument, użyj konstrukcji while / read / do:

while read i ; do command_name $i ; done < filename
Będzie
źródło
Powinienem był wspomnieć, zakładam, że używasz basha. Zdaję sobie sprawę, że istnieją inne powłoki, ale prawie wszystkie maszyny * nix, na których pracowałem, uruchamiały bash lub jego odpowiednik. IIRC, ta składnia powinna działać tak samo na ksh i zsh.
Will
1
Należy odczytać, read -rchyba że chcesz rozwinąć sekwencje ucieczki odwrotnym ukośnikiem - a NUL jest bezpieczniejszym separatorem niż nowa linia, szczególnie jeśli argumenty, które przekazujesz, to takie rzeczy jak nazwy plików, które mogą zawierać dosłowne znaki nowej linii. Ponadto bez czyszczenia IFS otrzymujesz początkowe i końcowe białe znaki niejawnie usuwane z i.
Charles Duffy,
18
command `< file`

przekaże zawartość pliku do polecenia na stdin, ale usunie znaki nowej linii, co oznacza, że ​​nie możesz iterować po każdej linii osobno. W tym celu możesz napisać skrypt z pętlą 'for':

for line in `cat input_file`; do some_command "$line"; done

Lub (wariant wieloliniowy):

for line in `cat input_file`
do
    some_command "$line"
done

Lub (wariant wieloliniowy z $()zamiast ``):

for line in $(cat input_file)
do
    some_command "$line"
done

Bibliografia:

  1. Składnia pętli: https://www.cyberciti.biz/faq/bash-for-loop/
Wesley Rice
źródło
Ponadto wprowadzenie < fileodwrotnych znaków oznacza, że ​​w rzeczywistości w ogóle nie wykonuje przekierowania command.
Charles Duffy,
3
(Czy ludzie, którzy głosowali za tym, faktycznie to przetestowali? command `< file` Nie działa ani w bashu, ani w POSIX sh; zsh to inna sprawa, ale nie o to chodzi w tym pytaniu).
Charles Duffy,
@CharlesDuffy Czy możesz bardziej szczegółowo opisać, jak to nie działa?
mwfearnley
@CharlesDuffy To działa w bash 3.2 i zsh 5.3. To nie zadziała w sh, ash i dash, ale ani w $ (<file).
Arnie97
1
@mwfearnley, z przyjemnością zapewniam tę specyfikę. Celem jest iteracja po liniach, prawda? Umieść Hello Worldw jednej linii. Zobaczysz go najpierw uruchomić some_command Hello, a następnie some_command World, nie some_command "Hello World" bądź some_command Hello World.
Charles Duffy
15

Robisz to za pomocą grawitacji:

echo World > file.txt
echo Hello `cat file.txt`
Tommy Lacroix
źródło
4
To nie tworzy się argumentu - bo to nie jest cytowany, to przedmiot do string-rozszczepienie, więc jeśli emitowany echo "Hello * Starry * World" > file.txtw pierwszym etapie, można uzyskać co najmniej cztery oddzielne argumenty przekazywane do drugiego polecenia - i prawdopodobnie więcej, ponieważ *s rozwiną się do nazw plików obecnych w bieżącym katalogu.
Charles Duffy,
3
... a ponieważ działa rozszerzanie glob, nie emituje dokładnie tego , co jest w pliku. A ponieważ wykonuje operację podstawiania poleceń, jest niezwykle nieefektywny - w rzeczywistości fork()wychodzi z podpowłoki z FIFO dołączonym do jej /bin/catstandardowego wyjścia , a następnie wywołuje jako element potomny tej podpowłoki, a następnie odczytuje wynik przez FIFO; porównaj z $(<file.txt), który wczytuje zawartość pliku bezpośrednio do basha, bez użycia podpowłok lub FIFO.
Charles Duffy,
11

Jeśli chcesz to zrobić w niezawodny sposób, który działa dla każdego możliwego argumentu wiersza poleceń (wartości ze spacjami, wartości ze znakami nowej linii, wartości ze znakami cudzysłowu, wartości niedrukowalne, wartości ze znakami glob, itp.), Dostaje trochę bardziej interesujące.


Aby zapisać do pliku, mając tablicę argumentów:

printf '%s\0' "${arguments[@]}" >file

... zastąpić "argument one", odpowiednio "argument two", itp.


Aby czytać z tego pliku i używać jego zawartości (w bash, ksh93 lub innej niedawnej powłoce z tablicami):

declare -a args=()
while IFS='' read -r -d '' item; do
  args+=( "$item" )
done <file
run_your_command "${args[@]}"

Aby czytać z tego pliku i używać jego zawartości (w powłoce bez tablic; zwróć uwagę, że spowoduje to nadpisanie lokalnej listy argumentów wiersza poleceń, a zatem najlepiej zrobić to wewnątrz funkcji, tak aby nadpisywać argumenty funkcji, a nie lista globalna):

set --
while IFS='' read -r -d '' item; do
  set -- "$@" "$item"
done <file
run_your_command "$@"

Zauważ, że -d(zezwalanie na użycie innego separatora końca linii) jest rozszerzeniem innym niż POSIX, a powłoka bez tablic również może go nie obsługiwać. W takim przypadku może być konieczne użycie języka innego niż powłoka, aby przekształcić zawartość rozdzielaną wartością NUL do postaci evalbezpiecznej:

quoted_list() {
  ## Works with either Python 2.x or 3.x
  python -c '
import sys, pipes, shlex
quote = pipes.quote if hasattr(pipes, "quote") else shlex.quote
print(" ".join([quote(s) for s in sys.stdin.read().split("\0")][:-1]))
  '
}

eval "set -- $(quoted_list <file)"
run_your_command "$@"
Charles Duffy
źródło
6

Oto jak przekazuję zawartość pliku jako argument do polecenia:

./foo --bar "$(cat ./bar.txt)"
chovy
źródło
1
Jest to - ale ze względu na to, że jest znacznie mniej wydajne - efektywnie równoważne z $(<bar.txt)(przy użyciu powłoki, takiej jak bash, z odpowiednim rozszerzeniem). $(<foo)jest przypadkiem specjalnym: w przeciwieństwie do zwykłego podstawiania poleceń, jak jest używane w $(cat ...), nie rozwidla podpowłoki do wykonywania operacji, a tym samym unika dużego narzutu.
Charles Duffy
3

Jeśli wszystko, co musisz zrobić, to włączyć plik arguments.txtz zawartością

arg1
arg2
argN

na my_command arg1 arg2 argNczym można po prostu użyć xargs:

xargs -a arguments.txt my_command

W xargswywołaniu możesz umieścić dodatkowe argumenty statyczne , takie jak xargs -a arguments.txt my_command staticArgwhich will callmy_command staticArg arg1 arg2 argN

Cobra_Fast
źródło
1

W mojej powłoce bash działało jak urok:

cat input_file | xargs -I % sh -c 'command1 %; command2 %; command3 %;'

gdzie plik_wejściowy to

arg1
arg2
arg3

Jak widać, pozwala to na wykonanie wielu poleceń w każdym wierszu z pliku input_file, niezła sztuczka, której się tutaj nauczyłem .

honkaboy
źródło
3
Twoja „fajna mała sztuczka” jest niebezpieczna z punktu widzenia bezpieczeństwa - jeśli masz argument zawierający $(somecommand), polecenie zostanie wykonane, a nie przekazane jako tekst. Podobnie >/etc/passwdzostanie przetworzone jako przekierowanie i nadpisanie /etc/passwd(jeśli uruchomione z odpowiednimi uprawnieniami) itp.
Charles Duffy
2
Zamiast tego znacznie bezpieczniej jest wykonać następujące czynności (w systemie z rozszerzeniami GNU): xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _- a także bardziej wydajne, ponieważ przekazuje jak najwięcej argumentów do każdej powłoki, zamiast uruchamiać po jednej powłoce w każdym wierszu pliku wejściowego.
Charles Duffy
I oczywiście strać bezużyteczne użyciecat
tripleee
0

Po kilkukrotnym edytowaniu odpowiedzi @Wesley Rice zdecydowałem, że moje zmiany stały się zbyt duże, aby kontynuować zmianę jego odpowiedzi zamiast pisać własną. Więc zdecydowałem, że muszę napisać własne!

Przeczytaj każdą linię pliku i wykonuj na niej linijka po linii w następujący sposób:

#!/bin/bash
input="/path/to/txt/file"
while IFS= read -r line
do
  echo "$line"
done < "$input"

Pochodzi bezpośrednio od autora Vivek Gite tutaj: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ . Dostaje kredyt!

Składnia: Czytaj plik wiersz po wierszu w powłoce Bash Unix i Linux:
1. Składnia jest następująca dla powłoki bash, ksh, zsh i wszystkich innych, aby odczytać plik wiersz po wierszu
2. while read -r line; do COMMAND; done < input.file
3. -rOpcja przekazana do polecenia odczytu zapobiega interpretacji znaków ucieczki odwrotnego ukośnika.
4. Dodaj IFS=opcję przed poleceniem odczytu, aby zapobiec przycinaniu wiodących / końcowych białych znaków -
5.while IFS= read -r line; do COMMAND_on $line; done < input.file


A teraz, aby odpowiedzieć na to teraz zamknięte pytanie, które też miałem: Czy jest możliwe dodanie listy plików z pliku za pomocą polecenia git? - oto moja odpowiedź:

Zauważ, że FILES_STAGEDjest to zmienna zawierająca bezwzględną ścieżkę do pliku, który zawiera kilka linii, gdzie każda linia jest ścieżką względną do pliku, na którym chciałbym to zrobić git add. Ten fragment kodu wkrótce stanie się częścią pliku „eRCaGuy_dotfiles / possible_scripts / sync_git_repo_to_build_machine.sh” w tym projekcie, aby umożliwić łatwą synchronizację tworzonych plików z jednego komputera (np. Komputera, na którym koduję ) z innym (np. mocniejszy komputer, na którym buduję): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles .

while IFS= read -r line
do
    echo "  git add \"$line\""
    git add "$line" 
done < "$FILES_STAGED"

Bibliografia:

  1. Skąd skopiowałem moją odpowiedź: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. Składnia pętli: https://www.cyberciti.biz/faq/bash-for-loop/

Związane z:

  1. Jak czytać zawartość pliku wiersz po wierszu i robić git addna nim: Czy jest możliwe dodanie listy plików z pliku za pomocą polecenia git?
Gabriel Staples
źródło