Come leggere un file in una variabile nella shell?


489

Voglio leggere un file e salvarlo in una variabile, ma devo conservare la variabile e non solo stampare il file. Come posso fare questo? Ho scritto questo script ma non è proprio quello di cui avevo bisogno:

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

Nel mio script, posso dare il nome del file come parametro, quindi, se il file contiene "aaaa", per esempio, lo stampa:

aaaa
11111-----

Ma questo stampa semplicemente il file sullo schermo e voglio salvarlo in una variabile! C'è un modo semplice per farlo?


1
Sembra essere un semplice testo. Se fosse un file binario, si avrebbe bisogno di questo , come il risultato di cato $(<someFile)si tradurrà in un'uscita incompleta (la dimensione è inferiore al file vero e proprio).
Aquarius Power il

Risposte:


1052

In multipiattaforma, il minimo comune denominatore shsi utilizza:

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

In basho zsh, per leggere un intero file in una variabile senza richiamare cat:

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

Invocare catin basho zshper trangugiare un file sarebbe considerato un inutile Uso di Cat .

Si noti che non è necessario citare la sostituzione del comando per preservare le nuove righe.

Vedi: Wiki di Bash Hacker - Sostituzione comandi - Specialità .


4
Ok ma è bash, non sh; potrebbe non adattarsi a tutti i casi.
Moala,

14
Non sarebbe value="`cat config.txt`"e value="$(<config.txt)"sarebbe più sicuro nel caso in cui config.txt contenga spazi?
Martin von Wittich,

13
Si noti che l'utilizzo catcome sopra non è sempre considerato un uso inutile di cat. Ad esempio, < invalid-file 2>/dev/nullgenererà un messaggio di errore che non può essere indirizzato a /dev/null, mentre cat invalid-file 2>/dev/nullviene indirizzato correttamente /dev/null.
Dejay Clayton,

16
Per i nuovi programmatori di shell come me, nota che la versione del gatto usa segni di spunta, non virgolette singole! Spero che questo salverà qualcuno mezz'ora mi ci è voluto per capirlo.
Ericksonla,

7
Per i nuovi sostenitori come me: nota che value=$(<config.txt)è buono, ma value = $(<config.txt)è cattivo. Fai attenzione a quegli spazi.
ArtHare

88

Se vuoi leggere l'intero file in una variabile:

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

Se vuoi leggerlo riga per riga:

while read line; do    
    echo $line    
done < file.txt

2
@brain: cosa succede se il file è Config.cpp e contiene barre rovesciate; virgolette doppie e virgolette?
user2284570,

2
Dovresti citare due volte la variabile in echo "$value". In caso contrario, la shell eseguirà la tokenizzazione degli spazi bianchi e l'espansione dei caratteri jolly sul valore.
triplo

3
@ user2284570 Usa read -rinvece di solo read- sempre, a meno che tu non richieda specificamente lo strano comportamento legacy a cui alludi.
triplo

74

Due insidie ​​importanti

che finora sono state ignorate da altre risposte:

  1. Trailing rimozione di newline dall'espansione dei comandi
  2. Rimozione del carattere NUL

Trailing rimozione di newline dall'espansione dei comandi

Questo è un problema per:

value="$(cat config.txt)"

soluzioni di tipo, ma non per readsoluzioni basate.

L'espansione dei comandi rimuove le nuove righe finali:

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

Uscite:

0000000 61
0000001

Questo rompe il metodo ingenuo di lettura dai file:

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

Soluzione alternativa POSIX: aggiungere un ulteriore carattere all'espansione del comando e rimuoverlo in seguito:

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

Uscite:

0000000 61 0a 0a
0000003

Soluzione quasi POSIX: codifica ASCII. Vedi sotto.

Rimozione del carattere NUL

Non esiste un modo Bash sano per memorizzare i caratteri NUL in variabili .

Ciò influisce sia sull'espansione che sulle readsoluzioni e non conosco alcuna soluzione alternativa.

Esempio:

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

Uscite:

0000000 61 00 62
0000003

0000000 61 62
0000002

Ah, il nostro NUL è sparito!

soluzioni alternative:

  • Codifica ASCII. Vedi sotto.

  • usa $""letterali di estensione bash :

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

    Funziona solo con valori letterali, quindi non è utile per leggere da file.

Soluzione alternativa per le insidie

Archivia una versione codificata uuencode base64 del file nella variabile e decodifica prima di ogni utilizzo:

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

Produzione:

0000000 61 00 0a
0000003

uuencode e udecode sono POSIX 7 ma non in Ubuntu 12.04 per impostazione predefinita ( sharutilspacchetto) ... Non vedo un'alternativa POSIX 7 per l' <()estensione di sostituzione del processo bash tranne la scrittura in un altro file ...

Naturalmente, questo è lento e scomodo, quindi immagino che la vera risposta sia: non usare Bash se il file di input può contenere caratteri NUL.


2
Grazie solo questo ha funzionato per me perché avevo bisogno di nuove linee.
Jason Livesay,

1
@CiroSantilli: Che dire se FILE è Config.cpp e contiene barre rovesciate; virgolette doppie e virgolette?
user2284570,

@ user2284570 Non lo sapevo, ma è facile da scoprire: S="$(printf "\\\'\"")"; echo $S. Uscita: \'". Quindi funziona =)
Ciro Santilli 13 冠状 病 六四 事件 法轮功

@CiroSantilli: su 5511 linee? Sei sicuro che non esiste un modo automatico?
user2284570,

@ user2284570 Non capisco, dove ci sono 5511 righe? Le insidie ​​derivano $()dall'espansione, il mio esempio mostra che l' $()espansione funziona \'".
Ciro Santilli 13 冠状 病 六四 事件 法轮功


2

Come Ciro Santilli nota, usando le sostituzioni di comando si lasceranno cadere nuove righe finali. La loro soluzione alternativa all'aggiunta di caratteri finali è ottima, ma dopo averlo usato per un po 'di tempo ho deciso che avevo bisogno di una soluzione che non usasse affatto la sostituzione dei comandi.

Il mio approccio ora usa readinsieme al flagprintf incorporato per leggere il contenuto di stdin direttamente in una variabile.-v

# 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"
}

Questo supporta input con o senza newline finali.

Esempio di utilizzo:

$ read_input file_contents < /tmp/file
# $file_contents now contains the contents of /tmp/file

Grande! Mi chiedo solo, perché non usare qualcosa di simile _contents="${_contents}${_line}\n "per preservare le nuove linee?
Eenoku,

1
Stai chiedendo del $'\n'? Questo è necessario, altrimenti stai aggiungendo letterali \ e npersonaggi. Il tuo blocco di codice ha anche uno spazio extra alla fine, non sono sicuro che sia intenzionale, ma indenterebbe ogni riga successiva con uno spazio extra.
dimo414,

Bene, grazie per la spiegazione!
Eenoku,

-3

Puoi accedere a 1 linea alla volta per il ciclo

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

Copia l'intero contenuto su File (ad esempio line.sh); Eseguire

chmod +x line.sh
./line.sh

Il tuo forloop non si sovrappone alle linee, ma passa alle parole. Nel caso di /etc/passwd, succede che ogni riga contiene solo una parola. Tuttavia, altri file possono contenere più parole per riga.
mpb
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.