Comment lire un fichier ou stdin en Bash?

dans Perl le code suivant se lira du fichier spécifié sur la ligne de commande args ou de stdin:

while (<>) {
   print($_);
}

C'est très pratique. Je veux juste savoir quelle est la façon la plus simple de lire à partir de file ou stdin à bash.

178
demandé sur codeforester 2011-08-08 13:27:56

14 réponses

la solution suivante lit à partir d'un fichier si le script est appelé avec un nom de fichier comme premier paramètre sinon à partir de l'entrée standard.

while read line
do
  echo "$line"
done < "${1:-/dev/stdin}"

la substitution ${1:-...} prend si défini autrement le nom de fichier de l'entrée standard du processus est utilisé.

299
répondu Fritz G. Mehner 2015-08-19 20:37:09

peut-être la solution la plus simple est de rediriger stdin avec un opérateur de redirection fusionnant:

#!/bin/bash
less <&0

Stdin est le descripteur de fichier zéro. Ce qui précède envoie l'entrée pipée à votre script bash dans le stdin de less.

plus d'informations à propos de la redirection du descripteur de fichier .

77
répondu Ryan Ballantyne 2013-05-10 20:48:52

Voici la manière la plus simple:

#!/bin/sh
cat -

Utilisation:

$ echo test | sh my_script.sh
test

pour affecter stdin à la variable, vous pouvez utiliser: STDIN=$(cat -) ou tout simplement STDIN=$(cat) comme l'opérateur n'est pas nécessaire (selon @mklement0 commentaire ).


pour analyser chaque ligne de la entrée standard , essayez le script suivant:

#!/bin/bash
while IFS= read -r line; do
  printf '%s\n' "$line"
done

pour lire à partir du fichier ou stdin (si l'argument n'est pas présent), vous pouvez l'étendre à:

#!/bin/bash
file=${1--} # POSIX-compliant; ${1:--} can be used either.
while IFS= read -r line; do
  printf '%s\n' "$line" # Or: env POSIXLY_CORRECT=1 echo "$line"
done < <(cat -- "$file")

Notes:

- read -r - ne pas traiter un caractère antislash d'une manière particulière. Considérez chaque antislash comme faisant partie de la ligne d'entrée.

- sans en plaçant IFS , par défaut les séquences de Space et Tab au début et à la fin des lignes sont ignorées (tronquées).

- utilisez printf au lieu de echo pour éviter d'imprimer des lignes vides lorsque la ligne se compose d'un seul -e , -n ou -E . Cependant, il ya une solution en utilisant env POSIXLY_CORRECT=1 echo "$line" qui exécute votre externe GNU echo qui le supporte. Voir: Comment puis-je echo "-e"?

Voir: Comment lire stdin quand aucun argument n'est passé? at stackoverflow SE

47
répondu kenorb 2017-05-23 12:02:48

la solution echo ajoute de nouvelles lignes chaque fois que IFS casse le flux d'entrée. la réponse de @fgm peut être légèrement modifiée:

cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
13
répondu David Souther 2017-05-23 12:02:48

je pense que c'est le straight-forward:

$ cat reader.sh
#!/bin/bash
while read line; do
  echo "reading: ${line}"
done < /dev/stdin

--

$ cat writer.sh
#!/bin/bash
for i in {0..5}; do
  echo "line ${i}"
done

--

$ ./writer.sh | ./reader.sh
reading: line 0
reading: line 1
reading: line 2
reading: line 3
reading: line 4
reading: line 5
12
répondu Amir Mehler 2014-03-26 19:25:26

la boucle Perl dans la question se lit de tous les arguments de nom de fichier sur la ligne de commande, ou de l'entrée standard si aucun fichier n'est spécifié. Les réponses que je vois tous semblent traiter un seul fichier ou une entrée standard s'il n'y a pas de fichier spécifié.

bien que souvent tourné avec précision comme UUOC (utilisation inutile de cat ), Il ya des moments où cat est le meilleur outil pour le travail, et il est discutable que c'est l'un d'eux:

cat "$@" |
while read -r line
do
    echo "$line"
done

le seul inconvénient de cette option est qu'elle crée un pipeline tournant dans un sous-shell, de sorte que des choses comme les assignations variables dans la boucle while ne sont pas accessibles à l'extérieur du pipeline. La bash , c'est-à-dire la Substitution de procédé :

while read -r line
do
    echo "$line"
done < <(cat "$@")

cela laisse la boucle while en cours d'exécution dans l'interpréteur de commandes principal, de sorte que les variables définies dans la boucle sont accessibles à l'extérieur boucle.

6
répondu Jonathan Leffler 2013-10-27 16:18:34

le comportement de Perl, avec le code donné dans L'OP peut prendre aucun ou plusieurs arguments, et si un argument est un seul trait d'Union - ceci est compris comme stdin. De plus, il est toujours possible d'avoir le nom du fichier avec $ARGV . Aucune des réponses données jusqu'à présent n'imite vraiment le comportement de Perl à cet égard. Voici une pure possibilité de Bash. Le truc est d'utiliser exec de façon appropriée.

#!/bin/bash

(($#)) || set -- -
while (($#)); do
   { [[  = - ]] || exec < ""; } &&
   while read -r; do
      printf '%s\n' "$REPLY"
   done
   shift
done

nom de fichier disponible dans .

si aucun argument n'est donné, nous définissons artificiellement - comme premier paramètre de position. On boucle ensuite sur les paramètres. Si un paramètre n'est pas - , nous redirigeons l'entrée standard à partir du nom de fichier avec exec . Si cette redirection réussit, on boucle avec une boucle while . J'utilise la variable standard REPLY , et dans ce cas vous n'avez pas besoin de réinitialiser IFS . Si vous voulez un autre nom, vous devez réinitialiser IFS comme (à moins que, bien sûr, vous ne voulez pas cela et savez ce que vous faites):

while IFS= read -r line; do
    printf '%s\n' "$line"
done
4
répondu gniourf_gniourf 2015-02-28 23:19:42

plus précisément...

while IFS= read -r line ; do
    printf "%s\n" "$line"
done < file
2
répondu Sorpigal 2011-08-08 12:51:51

veuillez essayer le code suivant:

while IFS= read -r line; do
    echo "$line"
done < file
2
répondu Webthusiast 2015-02-28 23:42:24

Code ${1:-/dev/stdin} va juste comprendre le premier argument, donc, que diriez-vous de ceci.

ARGS='$*'
if [ -z "$*" ]; then
  ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
   echo "$line"
done
1
répondu Takahiro Onodera 2015-10-08 15:10:37

ce qui suit fonctionne avec la norme sh (Testé avec dash sur Debian) et est assez lisible, mais c'est une question de goût:

if [ -n "" ]; then
    cat ""
else
    cat
fi | commands_and_transformations

détails: si le premier paramètre n'est pas vide alors cat ce fichier, sinon cat entrée standard. Puis la sortie de l'ensemble if est traitée par le commands_and_transformations .

0
répondu Notinlist 2015-03-30 14:47:26

Je ne trouve aucune de ces réponses acceptable. En particulier, la réponse acceptée ne traite que le premier paramètre de la ligne de commande et ignore le reste. Le programme Perl qu'il essaie d'émuler gère tous les paramètres de la ligne de commande. Donc la réponse acceptée ne répond même pas à la question. D'autres réponses utilisent des extensions bash, ajoutent des commandes 'cat' inutiles, ne fonctionnent que dans le cas simple de faire écho à des entrées à des sorties, ou sont simplement inutilement compliquées.

Cependant, je dois leur donner un peu de crédit, car ils m'ont donné quelques idées. Voici la réponse complète:

#!/bin/sh

if [ $# = 0 ]
then
        DEFAULT_INPUT_FILE=/dev/stdin
else
        DEFAULT_INPUT_FILE=
fi

# Iterates over all parameters or /dev/stdin
for FILE in "$@" $DEFAULT_INPUT_FILE
do
        while IFS= read -r LINE
        do
                # Do whatever you want with LINE here.
                echo $LINE
        done < "$FILE"
done
0
répondu Gungwald 2018-03-05 17:11:08

j'ai combiné toutes les réponses ci-dessus et créé une fonction shell qui répondrait à mes besoins. Ceci provient d'un terminal cygwin de mes 2 machines Windows10 où j'avais un dossier partagé entre elles. Je dois être capable de gérer ce qui suit:

  • cat file.cpp | tx
  • tx < file.cpp
  • tx file.cpp

Lorsqu'un nom de fichier spécifique est spécifié, je dois utiliser le même nom de fichier lors de la copie. Lorsque le flux de données d'entrée a été acheminé par l'intermédiaire de la piped, alors je dois générer un nom de fichier temporaire ayant l'heure minute et secondes. Le mainfolder partagé a des sous-dossiers des jours de la semaine. C'est pour des raisons d'organisation.

voici le scénario ultime pour mes besoins:

tx ()
{
  if [ $# -eq 0 ]; then
    local TMP=/tmp/tx.$(date +'%H%M%S')
    while IFS= read -r line; do
        echo "$line"
    done < /dev/stdin > $TMP
    cp $TMP //$OTHER/stargate/$(date +'%a')/
    rm -f $TMP
  else
    [ -r  ] && cp  //$OTHER/stargate/$(date +'%a')/ || echo "cannot read file"
  fi
}

S'il y a un moyen que vous pouvez voir pour optimiser davantage ceci, je voudrais savoir.

0
répondu ifelsemonkey 2018-09-28 18:17:38

Que Diriez-vous de

for line in `cat`; do
    something($line);
done
-1
répondu Charles Cooper 2014-09-05 21:03:39