Comment lire un fichier dans une variable en shell?


489

Je veux lire un fichier et l'enregistrer dans une variable, mais je dois conserver la variable et pas seulement imprimer le fichier. Comment puis-je faire ceci? J'ai écrit ce script mais ce n'est pas tout à fait ce dont j'avais besoin:

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

Dans mon script, je peux donner le nom du fichier comme paramètre, donc, si le fichier contient "aaaa", par exemple, il afficherait ceci:

aaaa
11111-----

Mais cela imprime simplement le fichier à l'écran et je veux l'enregistrer dans une variable! Y a-t-il un moyen facile de faire ceci?


1
Il semble que ce soit un texte simple. Si c'était un fichier binaire, vous avez besoin ce , à la suite de catou $(<someFile)se traduira par une sortie incomplète ( la taille est inférieure à celle du fichier réel).
Aquarius Power

Réponses:


1052

Dans le plus petit dénominateur commun multiplateforme que shvous utilisez:

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

Dans bashou zsh, pour lire un fichier entier dans une variable sans invoquer cat:

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

L' appel catdans bashou zshà slurp un fichier serait considéré comme un Useless utilisation de Cat .

Notez qu'il n'est pas nécessaire de citer la substitution de commande pour conserver les retours à la ligne.

Voir: Wiki de Bash Hacker - Substitution de commandes - Spécialités .


4
Ok mais c'est bash, pas sh; il peut ne pas convenir à tous les cas.
moala

14
Ne serait pas value="`cat config.txt`"et value="$(<config.txt)"plus sûr dans le cas où config.txt contient des espaces?
Martin von Wittich

13
Notez que l'utilisation catcomme ci-dessus n'est pas toujours considérée comme une utilisation inutile de cat. Par exemple, < invalid-file 2>/dev/nullentraînera un message d'erreur qui ne peut pas être acheminé vers /dev/null, alors cat invalid-file 2>/dev/nullqu'il est correctement acheminé vers /dev/null.
Dejay Clayton

16
Pour les nouveaux scripteurs shell comme moi, notez que la version cat utilise des tiques inverses, pas des guillemets simples! J'espère que cela permettra à quelqu'un d'économiser une demi-heure, il m'a fallu le comprendre.
ericksonla

7
Pour les nouveaux bashers comme moi: Notez que value=$(<config.txt)c'est bien, mais value = $(<config.txt)c'est mauvais. Attention à ces espaces.
ArtHare

88

Si vous souhaitez lire l'intégralité du fichier dans une variable:

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

Si vous souhaitez le lire ligne par ligne:

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

2
@brain: que faire si le fichier est Config.cpp et contient des barres obliques inverses; guillemets doubles et citations?
user2284570

2
Vous devez entre guillemets la variable dans echo "$value". Sinon, le shell effectuera la tokenisation des espaces et l'expansion des caractères génériques sur la valeur.
tripleee

3
@ user2284570 Utilisez read -rau lieu de juste read- toujours, sauf si vous avez spécifiquement besoin du comportement hérité étrange auquel vous faites allusion.
tripleee

74

Deux écueils importants

qui ont été ignorées par d'autres réponses jusqu'à présent:

  1. Suppression de la nouvelle ligne de fin de l'extension de commande
  2. Suppression de caractère NUL

Suppression de la nouvelle ligne de fin de l'extension de commande

C'est un problème pour:

value="$(cat config.txt)"

solutions de type, mais pas pour les readsolutions basées.

L'expansion de la commande supprime les nouvelles lignes de fin:

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

Les sorties:

0000000 61
0000001

Cela rompt la méthode naïve de lecture à partir de fichiers:

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

Solution de contournement POSIX: ajoutez un caractère supplémentaire à l'extension de commande et supprimez-le ultérieurement:

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

Les sorties:

0000000 61 0a 0a
0000003

Solution de contournement presque POSIX: encodage ASCII. Voir ci-dessous.

Suppression de caractère NUL

Il n'y a aucun moyen sain et simple de stocker des caractères NUL dans des variables .

Cela affecte à la fois l'expansion et les readsolutions, et je ne connais pas de bonne solution de contournement.

Exemple:

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

Les sorties:

0000000 61 00 62
0000003

0000000 61 62
0000002

Ha, notre NUL est parti!

Solutions de contournement:

  • Encodage ASCII. Voir ci-dessous.

  • utilisez les $""littéraux d' extension bash :

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

    Fonctionne uniquement pour les littéraux, donc pas utile pour lire à partir de fichiers.

Solution aux écueils

Stockez une version encodée en uuencode base64 du fichier dans la variable et décodez avant chaque utilisation:

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

Production:

0000000 61 00 0a
0000003

uuencode et udecode sont POSIX 7 mais pas dans Ubuntu 12.04 par défaut ( sharutilspackage) ... Je ne vois pas d'alternative POSIX 7 pour l' <()extension de substitution de processus bash sauf pour écrire dans un autre fichier ...

Bien sûr, cela est lent et peu pratique, donc je suppose que la vraie réponse est: n'utilisez pas Bash si le fichier d'entrée peut contenir des caractères NUL.


2
Merci seulement celui-ci a fonctionné pour moi car j'avais besoin de nouvelles lignes.
Jason Livesay

1
@CiroSantilli: Qu'en est-il si FILE est Config.cpp et contient des barres obliques inverses; guillemets doubles et citations?
user2284570

@ user2284570 Je ne savais pas, mais il est facile de savoir: S="$(printf "\\\'\"")"; echo $S. Sortie: \'". Donc ça marche =)
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

@CiroSantilli: Sur 5511 lignes? Êtes-vous sûr qu'il n'existe aucun moyen automatisé?
user2284570

@ user2284570 Je ne comprends pas, où il y a 5511 lignes? Les pièges proviennent de l' $()expansion, mon exemple montre que l' $()expansion fonctionne avec \'".
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功


2

Comme le note Ciro Santilli, l' utilisation de substitutions de commandes supprimera les nouvelles lignes de fin. Leur solution de contournement ajoutant des caractères de fin est excellente, mais après l'avoir utilisé pendant un certain temps, j'ai décidé que j'avais besoin d'une solution qui n'utilisait pas du tout la substitution de commandes.

Mon approche utilise maintenant readavec le drapeauprintf intégré afin de lire le contenu de stdin directement dans une variable.-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"
}

Cela prend en charge les entrées avec ou sans retour à la ligne.

Exemple d'utilisation:

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

Génial! Je me demande juste, pourquoi ne pas utiliser quelque chose comme _contents="${_contents}${_line}\n "pour préserver les nouvelles lignes?
Eenoku

1
Demandez-vous à propos de la $'\n'? C'est nécessaire, sinon vous ajoutez du littéral \ et des ncaractères. Votre bloc de code a également un espace supplémentaire à la fin, vous ne savez pas si c'est intentionnel, mais il indenterait chaque ligne suivante avec un espace supplémentaire.
dimo414

Eh bien, merci pour l'explication!
Eenoku

-3

Vous pouvez accéder à 1 ligne à la fois par boucle

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

Copiez tout le contenu dans File (disons line.sh); Exécuter

chmod +x line.sh
./line.sh

Votre forboucle ne boucle pas sur les lignes, elle boucle sur les mots. Dans le cas de /etc/passwd, il arrive que chaque ligne ne contienne qu'un seul mot. Cependant, d'autres fichiers peuvent contenir plusieurs mots par ligne.
mpb
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.