Wie verwende ich die Zeilen einer Datei als Argumente eines Befehls?

158

Angenommen, ich habe eine Datei foo.txtmit NArgumenten

arg1
arg2
...
argN

was ich an den Befehl übergeben muss my_command

Wie verwende ich die Zeilen einer Datei als Argumente eines Befehls?

Yoo
quelle
10
"Argumente", Plural oder "Argument", einzeln? Die akzeptierte Antwort ist nur im Fall mit einem Argument korrekt - worüber der Text nachfragt -, nicht jedoch im Fall mit mehreren Argumenten, zu dem das Thema zu fragen scheint.
Charles Duffy
Die 4 Antworten unten haben normalerweise identische Ergebnisse, jedoch eine leicht unterschiedliche Semantik. Jetzt erinnere ich mich, warum ich aufgehört habe, Bash-Skripte zu schreiben: P
Navin

Antworten:

236

Wenn Ihre Shell (unter anderem) bash ist, ist eine Verknüpfung für $(cat afile)is $(< afile), also würden Sie schreiben:

mycommand "$(< file.txt)"

Dokumentiert in der Bash-Manpage im Abschnitt 'Befehlsersetzung'.

Lassen Sie Ihren Befehl alternativ von stdin lesen, also: mycommand < file.txt

Glenn Jackman
quelle
11
Um pedantisch zu sein, ist es keine Abkürzung, den <Operator zu verwenden. Dies bedeutet, dass die Shell selbst die Umleitung ausführt, anstatt die catBinärdatei für ihre Umleitungseigenschaften auszuführen .
lstyls
2
Da es sich um eine Kerneleinstellung handelt, müssen Sie den Kernel neu kompilieren. Oder untersuchen Sie den Befehl xargs
Glenn Jackman
3
@ykaner, weil ohne "$(…)", der Inhalt von file.txtan die Standardeingabe von übergeben würde mycommand, nicht als Argument. "$(…)"bedeutet , den Befehl auszuführen und die Ausgabe als Zeichenfolge zurückzugeben ; hier liest der "Befehl" nur die Datei, aber es könnte komplexer sein.
Törzsmókus
3
Es funktioniert nicht für mich, wenn ich nicht die Zitate verliere. Bei den Anführungszeichen wird die gesamte Datei als 1 Argument verwendet. Ohne Anführungszeichen wird jede Zeile als separates Argument interpretiert.
Pete
1
@LarsH du bist absolut richtig. Das ist weniger verwirrend, danke.
lstyls
37

Wie bereits erwähnt, können Sie die Backticks oder verwenden $(cat filename).

Was nicht erwähnt wurde, und ich denke, es ist wichtig anzumerken, ist, dass Sie sich daran erinnern müssen, dass die Shell den Inhalt dieser Datei nach Leerzeichen aufteilt und jedes "Wort", das sie findet, Ihrem Befehl als Argument gibt. Und obwohl Sie möglicherweise ein Befehlszeilenargument in Anführungszeichen setzen können, damit es Leerzeichen, Escape-Sequenzen usw. enthält, funktioniert das Lesen aus der Datei nicht dasselbe. Zum Beispiel, wenn Ihre Datei enthält:

a "b c" d

Die Argumente, die Sie erhalten, sind:

a
"b
c"
d

Wenn Sie jede Zeile als Argument ziehen möchten, verwenden Sie das Konstrukt while / read / do:

while read i ; do command_name $i ; done < filename
Wille
quelle
Ich hätte erwähnen sollen, ich gehe davon aus, dass Sie Bash verwenden. Mir ist klar, dass es da draußen noch andere Shells gibt, aber fast alle * nix-Maschinen, an denen ich gearbeitet habe, haben entweder Bash oder ein gleichwertiges Programm ausgeführt. IIRC, diese Syntax sollte auf ksh und zsh gleich funktionieren.
Will
1
Das Lesen sollte erfolgen, read -res sei denn, Sie möchten Backslash-Escape-Sequenzen erweitern - und NUL ist ein sicherer Trennzeichen als die Newline, insbesondere wenn es sich bei den übergebenen Argumenten um Dateinamen handelt, die wörtliche Newlines enthalten können. Ohne IFS zu löschen, werden implizit führende und nachfolgende Leerzeichen gelöscht i.
Charles Duffy
18
command `< file`

Übergibt den Dateiinhalt an den Befehl auf stdin, entfernt jedoch Zeilenumbrüche, sodass Sie nicht jede Zeile einzeln durchlaufen können. Dafür könnten Sie ein Skript mit einer 'for'-Schleife schreiben:

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

Oder (die mehrzeilige Variante):

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

Oder (mehrzeilige Variante mit $()statt ``):

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

Verweise:

  1. Für die Schleifensyntax: https://www.cyberciti.biz/faq/bash-for-loop/
Wesley Rice
quelle
Das < fileEinfügen von Backticks bedeutet auch, dass überhaupt keine Umleitung durchgeführt wird command.
Charles Duffy
3
(Haben die Leute, die dies bewertet haben, es tatsächlich getestet? Funktioniert command `< file` weder in Bash noch in POSIX sh; zsh ist eine andere Sache, aber das ist nicht die Shell, um die es in dieser Frage geht).
Charles Duffy
@CharlesDuffy Kannst du genauer sagen, wie es nicht funktioniert?
mwfearnley
@CharlesDuffy Dies funktioniert in Bash 3.2 und Zsh 5.3. Dies funktioniert nicht in sh, ash und dash, aber auch nicht in $ (<file).
Arnie97
1
@mwfearnley, gerne diese Spezifität zur Verfügung stellen. Das Ziel ist es, über Linien zu iterieren, oder? Setzen Sie Hello Worldeine Zeile. Sie werden sehen , es zuerst ausgeführt some_command Hello, dann some_command World, nicht some_command "Hello World" oder some_command Hello World.
Charles Duffy
15

Sie tun dies mit Backticks:

echo World > file.txt
echo Hello `cat file.txt`
Tommy Lacroix
quelle
4
Dies gilt nicht schaffen ein Argument - weil es nicht zitiert wird, ist es unter String-Splitting, wenn Sie also emittiert echo "Hello * Starry * World" > file.txtim ersten Schritt, Sie mindestens vier separate Argumente auf den zweiten Befehl übergeben bekommen würde - und wahrscheinlich mehr, da das *s auf die Namen der Dateien erweitert würde, die im aktuellen Verzeichnis vorhanden sind.
Charles Duffy
3
... und weil es eine Glob-Erweiterung ausführt, gibt es nicht genau das aus , was in der Datei enthalten ist. Und weil es eine Befehlsersetzungsoperation ausführt, ist es äußerst ineffizient - es fork()löst tatsächlich eine Unterschale mit einem an seine Standardausgabe angehängten FIFO aus, ruft dann /bin/catals untergeordnetes Element dieser Unterschale auf und liest dann die Ausgabe durch das FIFO; Vergleichen Sie mit $(<file.txt), wodurch der Inhalt der Datei direkt in Bash gelesen wird, ohne dass Subshells oder FIFOs beteiligt sind.
Charles Duffy
11

Wenn Sie dies auf robuste Weise tun möchten, die für jedes mögliche Befehlszeilenargument funktioniert (Werte mit Leerzeichen, Werte mit Zeilenumbrüchen, Werte mit wörtlichen Anführungszeichen, nicht druckbare Werte, Werte mit Glob-Zeichen usw.), wird es etwas interessanter.


So schreiben Sie in eine Datei mit einer Reihe von Argumenten:

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

... ersetzen "argument one", "argument two"usw. als angemessen.


So lesen Sie aus dieser Datei und verwenden ihren Inhalt (in bash, ksh93 oder einer anderen aktuellen Shell mit Arrays):

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

Um aus dieser Datei zu lesen und ihren Inhalt zu verwenden (in einer Shell ohne Arrays; beachten Sie, dass dies Ihre lokale Befehlszeilenargumentliste überschreibt und daher am besten innerhalb einer Funktion ausgeführt wird, sodass Sie die Argumente der Funktion überschreiben und nicht die globale Liste):

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

Beachten Sie, dass -d(die Verwendung eines anderen Zeilenende-Trennzeichens) eine Nicht-POSIX-Erweiterung ist und eine Shell ohne Arrays diese möglicherweise auch nicht unterstützt. In diesem Fall müssen Sie möglicherweise eine Nicht-Shell-Sprache verwenden, um den durch NUL getrennten Inhalt in eine evalsichere Form umzuwandeln:

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
quelle
6

So übergebe ich den Inhalt einer Datei als Argument an einen Befehl:

./foo --bar "$(cat ./bar.txt)"
chovy
quelle
1
Dies ist - aber um wesentlich weniger effizient zu sein - effektiv gleichbedeutend mit $(<bar.txt)(bei Verwendung einer Shell wie Bash mit der entsprechenden Erweiterung). $(<foo)ist ein Sonderfall: Im Gegensatz zur regulären Befehlssubstitution, wie sie in verwendet wird $(cat ...), wird eine Subshell nicht für den Betrieb freigegeben und somit ein hoher Overhead vermieden.
Charles Duffy
3

Wenn Sie nur eine Datei arguments.txtmit Inhalten drehen müssen

arg1
arg2
argN

in my_command arg1 arg2 argNdann können Sie einfach verwenden xargs:

xargs -a arguments.txt my_command

Sie können dem xargsAufruf zusätzliche statische Argumente hinzufügen, wie xargs -a arguments.txt my_command staticArgsie aufgerufen werdenmy_command staticArg arg1 arg2 argN

Cobra_Fast
quelle
1

In meiner Bash-Shell funktionierte Folgendes wie ein Zauber:

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

wo input_file ist

arg1
arg2
arg3

Auf diese Weise können Sie mit jeder Zeile aus input_file mehrere Befehle ausführen, ein netter kleiner Trick, den ich hier gelernt habe .

Honkaboy
quelle
3
Ihr "netter kleiner Trick" ist aus Sicherheitsgründen gefährlich. Wenn Sie ein Argument enthalten $(somecommand), wird dieser Befehl ausgeführt und nicht als Text weitergegeben. Ebenso >/etc/passwdwird als Umleitung verarbeitet und überschrieben /etc/passwd(wenn mit entsprechenden Berechtigungen ausgeführt) usw.
Charles Duffy
2
Es ist viel sicherer, stattdessen Folgendes zu tun (auf einem System mit GNU-Erweiterungen): xargs -d $'\n' sh -c 'for arg; do command1 "$arg"; command2 "arg"; command3 "arg"; done' _- und auch effizienter, da so viele Argumente wie möglich an jede Shell übergeben werden, anstatt eine Shell pro Zeile in Ihrer Eingabedatei zu starten.
Charles Duffy
Und natürlich verlieren Sie die nutzlose Verwendung voncat
Tripleee
0

Nachdem ich die Antwort von @Wesley Rice ein paar Mal bearbeitet hatte, entschied ich, dass meine Änderungen einfach zu groß wurden, um seine Antwort weiter zu ändern, anstatt meine eigene zu schreiben. Also habe ich beschlossen, dass ich meine eigenen schreiben muss!

Lesen Sie jede Zeile einer Datei ein und bearbeiten Sie sie zeilenweise wie folgt:

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

Dies kommt direkt vom Autor Vivek Gite hier: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/ . Er bekommt den Kredit!

Syntax: Datei zeilenweise in einer Bash Unix- und Linux-Shell lesen:
1. Die Syntax für bash, ksh, zsh und alle anderen Shells lautet wie folgt, um eine Datei zeilenweise zu lesen
. while read -r line; do COMMAND; done < input.file
3. Die -rOption wurde an den Befehl read übergeben verhindert, dass Backslash-Escapezeichen interpretiert werden.
4. Fügen Sie IFS=vor dem Lesebefehl eine Option hinzu, um zu verhindern, dass führende / nachfolgende Leerzeichen gekürzt werden -
5.while IFS= read -r line; do COMMAND_on $line; done < input.file


Und nun, um diese jetzt geschlossene Frage zu beantworten, die ich auch hatte: Ist es möglich, eine Liste von Dateien aus einer Datei hinzuzufügen? - Hier ist meine Antwort:

Beachten Sie, dass dies FILES_STAGEDeine Variable ist, die den absoluten Pfad zu einer Datei enthält, die eine Reihe von Zeilen enthält, wobei jede Zeile ein relativer Pfad zu einer Datei ist, für die ich etwas tun möchte git add. Dieses Code-Snippet wird bald Teil der Datei "eRCaGuy_dotfiles / nützliche_skripte / sync_git_repo_to_build_machine.sh" in diesem Projekt, um die einfache Synchronisierung von Dateien in der Entwicklung von einem PC ( z. B. einem Computer, auf dem ich codiere) auf einen anderen ( z. B. a) zu ermöglichen leistungsstärkerer Computer, auf dem ich aufbaue): https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles .

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

Verweise:

  1. Wo ich meine Antwort kopiert habe von: https://www.cyberciti.biz/faq/unix-howto-read-line-by-line-from-file/
  2. Für die Schleifensyntax: https://www.cyberciti.biz/faq/bash-for-loop/

Verbunden:

  1. So lesen Sie den Inhalt einer Datei zeilenweise und führen git addihn aus: Ist es möglich, eine Liste von Dateien aus einer Datei hinzuzufügen?
Gabriel Staples
quelle