Wie lese ich eine Datei in eine Variable in der Shell?

489

Ich möchte eine Datei lesen und in einer Variablen speichern, aber ich muss die Variable behalten und nicht nur die Datei ausdrucken. Wie kann ich das machen? Ich habe dieses Skript geschrieben, aber es ist nicht ganz das, was ich brauchte:

#!/bin/sh
while read LINE  
do  
  echo $LINE  
done <$1  
echo 11111-----------  
echo $LINE  

In meinem Skript kann ich den Dateinamen als Parameter angeben. Wenn die Datei beispielsweise "aaaa" enthält, wird Folgendes ausgedruckt:

aaaa
11111-----

Dies druckt die Datei jedoch nur auf dem Bildschirm aus und ich möchte sie in einer Variablen speichern! Gibt es eine einfache Möglichkeit, dies zu tun?

Kaka
quelle
1
Es scheint ein einfacher Text zu sein. Wenn es sich um eine Binärdatei handelt, benötigen Sie diese als Ergebnis einer unvollständigen Ausgabe catoder $(<someFile)führen zu einer unvollständigen Ausgabe (die Größe ist geringer als die tatsächliche Datei).
Wassermann Power

Antworten:

1052

Im plattformübergreifenden kleinsten gemeinsamen Nenner verwenden shSie:

#!/bin/sh
value=`cat config.txt`
echo "$value"

In bashoder zsh, um eine ganze Datei in eine Variable einzulesen, ohne sie aufzurufen cat:

#!/bin/bash
value=$(<config.txt)
echo "$value"

Das Aufrufen catin bashoder um zsheine Datei zu schlürfen wäre in Betracht gezogen wird Useless Verwendung von Cat .

Beachten Sie, dass es nicht erforderlich ist, die Befehlsersetzung anzugeben, um Zeilenumbrüche beizubehalten.

Siehe: Bash Hacker's Wiki - Befehlsersetzung - Spezialitäten .

Alan Gutierrez
quelle
4
Ok, aber es ist Bash, nicht Sh; Es passt möglicherweise nicht in alle Fälle.
Moala
14
Wäre value="`cat config.txt`"und wäre es value="$(<config.txt)"nicht sicherer, wenn config.txt Leerzeichen enthält?
Martin von Wittich
13
Beachten Sie, dass die Verwendung catwie oben nicht immer als nutzlose Verwendung von angesehen wird cat. Zum Beispiel < invalid-file 2>/dev/nullwird in einer Fehlermeldung, die nicht weitergeleitet werden /dev/null, während cat invalid-file 2>/dev/nullkorrekt weitergeleitet bekommt /dev/null.
Dejay Clayton
16
Beachten Sie für neue Shell-Scripter wie mich, dass die Katzenversion Back Ticks verwendet, keine einfachen Anführungszeichen! Hoffentlich spart dies jemandem eine halbe Stunde, die ich gebraucht habe, um es herauszufinden.
Ericksonla
7
Für neue Bashers wie mich: Beachten Sie, dass value=$(<config.txt)das gut, aber value = $(<config.txt)schlecht ist. Achten Sie auf diese Räume.
ArtHare
88

Wenn Sie die gesamte Datei in eine Variable einlesen möchten:

#!/bin/bash
value=`cat sources.xml`
echo $value

Wenn Sie es Zeile für Zeile lesen möchten:

while read line; do    
    echo $line    
done < file.txt
Gehirn
quelle
2
@brain: Was ist, wenn die Datei Config.cpp ist und Backslashes enthält? doppelte Anführungszeichen und Anführungszeichen?
user2284570
2
Sie sollten die Variable in doppelt zitieren echo "$value". Andernfalls führt die Shell eine Whitespace-Tokenisierung und eine Platzhaltererweiterung für den Wert durch.
Tripleee
3
@ user2284570 Verwenden Sie read -rstatt nur read- immer, es sei denn, Sie benötigen speziell das seltsame Legacy-Verhalten, auf das Sie anspielen.
Tripleee
74

Zwei wichtige Fallstricke

die bisher von anderen Antworten ignoriert wurden:

  1. Nachfolgendes Entfernen von Zeilenumbrüchen aus der Befehlserweiterung
  2. NUL Zeichenentfernung

Nachfolgendes Entfernen von Zeilenumbrüchen aus der Befehlserweiterung

Dies ist ein Problem für:

value="$(cat config.txt)"

Typ Lösungen, aber nicht für readbasierte Lösungen.

Die Befehlserweiterung entfernt nachgestellte Zeilenumbrüche:

S="$(printf "a\n")"
printf "$S" | od -tx1

Ausgänge:

0000000 61
0000001

Dies unterbricht die naive Methode zum Lesen aus Dateien:

FILE="$(mktemp)"
printf "a\n\n" > "$FILE"
S="$(<"$FILE")"
printf "$S" | od -tx1
rm "$FILE"

POSIX-Problemumgehung: Fügen Sie der Befehlserweiterung ein zusätzliches Zeichen hinzu und entfernen Sie es später:

S="$(cat $FILE; printf a)"
S="${S%a}"
printf "$S" | od -tx1

Ausgänge:

0000000 61 0a 0a
0000003

Fast POSIX-Problemumgehung: ASCII-Codierung. Siehe unten.

NUL Zeichenentfernung

Es gibt keine vernünftige Bash-Methode, um NUL-Zeichen in Variablen zu speichern .

Dies wirkt sich sowohl auf die Erweiterung als auch auf die readLösungen aus, und ich kenne keine gute Problemumgehung dafür.

Beispiel:

printf "a\0b" | od -tx1
S="$(printf "a\0b")"
printf "$S" | od -tx1

Ausgänge:

0000000 61 00 62
0000003

0000000 61 62
0000002

Ha, unser NUL ist weg!

Problemumgehungen:

  • ASCII-Codierung. Siehe unten.

  • Verwenden Sie Bash-Erweiterungsliterale $"":

    S=$"a\0b"
    printf "$S" | od -tx1

    Funktioniert nur für Literale und ist daher nicht zum Lesen aus Dateien geeignet.

Problemumgehung für die Fallstricke

Speichern Sie eine uuencode base64-codierte Version der Datei in der Variablen und dekodieren Sie sie vor jeder Verwendung:

FILE="$(mktemp)"
printf "a\0\n" > "$FILE"
S="$(uuencode -m "$FILE" /dev/stdout)"
uudecode -o /dev/stdout <(printf "$S") | od -tx1
rm "$FILE"

Ausgabe:

0000000 61 00 0a
0000003

uuencode und udecode sind POSIX 7, aber standardmäßig nicht in Ubuntu 12.04 ( sharutilsPaket) ... Ich sehe keine POSIX 7-Alternative für die Bash-Prozess- <()Ersetzungserweiterung, außer in eine andere Datei zu schreiben ...

Dies ist natürlich langsam und unpraktisch, daher lautet die eigentliche Antwort: Verwenden Sie Bash nicht, wenn die Eingabedatei NUL-Zeichen enthalten kann.

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
2
Danke, nur dieser hat für mich funktioniert, weil ich Zeilenumbrüche brauchte.
Jason Livesay
1
@CiroSantilli: Was ist, wenn FILE Config.cpp ist und Backslashes enthält? doppelte Anführungszeichen und Anführungszeichen?
user2284570
@ user2284570 Ich wusste es nicht, aber es ist leicht herauszufinden : S="$(printf "\\\'\"")"; echo $S. Ausgabe : \'". So funktioniert es =)
Ciro Santilli 法轮功 冠状 病 六四 事件 法轮功
@CiroSantilli: Auf 5511 Linien? Sind Sie sicher, dass es keinen automatisierten Weg gibt?
user2284570
@ user2284570 Ich verstehe nicht, wo es 5511 Zeilen gibt? Die Fallstricke ergeben sich aus der $()Erweiterung. Mein Beispiel zeigt, dass die $()Erweiterung mit funktioniert \'".
Ciro Santilli 法轮功 冠状 病 六四 事件 13
3

das funktioniert bei mir: v=$(cat <file_path>) echo $v

angelo.mastro
quelle
2

Wie Ciro Santilli mit Befehlsersetzungen feststellt , werden nachfolgende Zeilenumbrüche gelöscht. Die Problemumgehung beim Hinzufügen von nachgestellten Zeichen ist großartig, aber nachdem ich sie einige Zeit verwendet hatte, entschied ich, dass ich eine Lösung brauchte, die überhaupt keine Befehlsersetzung verwendete.

Mein Ansatz verwendet jetzt readzusammen mit dem printfeingebauten -vFlag , um den Inhalt von stdin direkt in eine Variable einzulesen.

# Reads stdin into a variable, accounting for trailing newlines. Avoids needing a subshell or
# command substitution.
read_input() {
  # Use unusual variable names to avoid colliding with a variable name
  # the user might pass in (notably "contents")
  : "${1:?Must provide a variable to read into}"
  if [[ "$1" == '_line' || "$1" == '_contents' ]]; then
    echo "Cannot store contents to $1, use a different name." >&2
    return 1
  fi

  local _line _contents
   while read -r _line; do
     _contents="${_contents}${_line}"$'\n'
   done
   _contents="${_contents}${_line}" # capture any content after the last newline
   printf -v "$1" '%s' "$_contents"
}

Dies unterstützt Eingaben mit oder ohne nachgestellte Zeilenumbrüche.

Anwendungsbeispiel:

$ read_input file_contents < /tmp/file
# $file_contents now contains the contents of /tmp/file
dimo414
quelle
Großartig! Ich frage mich nur, warum ich nicht so etwas verwenden sollte _contents="${_contents}${_line}\n ", um Zeilenumbrüche zu erhalten.
Eenoku
1
Fragen Sie nach dem $'\n'? Das ist notwendig, sonst hängen Sie Literale \ und nZeichen an. Ihr Codeblock hat am Ende auch einen zusätzlichen Platz, nicht sicher, ob dies beabsichtigt ist, aber er würde jede nachfolgende Zeile mit einem zusätzlichen Leerzeichen einrücken.
dimo414
Nun, danke für die Erklärung!
Eenoku
-3

Sie können mit der for-Schleife jeweils auf 1 Zeile zugreifen

#!/bin/bash -eu

#This script prints contents of /etc/passwd line by line

FILENAME='/etc/passwd'
I=0
for LN in $(cat $FILENAME)
do
    echo "Line number $((I++)) -->  $LN"
done

Kopieren Sie den gesamten Inhalt in die Datei (z. B. line.sh). Ausführen

chmod +x line.sh
./line.sh
Prakash D.
quelle
Ihre forSchleife durchläuft keine Zeilen, sondern Wörter. Im Fall von /etc/passwdkommt es vor, dass jede Zeile nur ein Wort enthält. Andere Dateien können jedoch mehrere Wörter pro Zeile enthalten.
mpb