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.
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é.
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 .
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 deecho
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 utilisantenv POSIXLY_CORRECT=1 echo "$line"
qui exécute votre externe GNUecho
qui le supporte. Voir: Comment puis-je echo "-e"?
Voir: Comment lire stdin quand aucun argument n'est passé? at stackoverflow SE
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}"
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
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.
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
plus précisément...
while IFS= read -r line ; do
printf "%s\n" "$line"
done < file
veuillez essayer le code suivant:
while IFS= read -r line; do
echo "$line"
done < file
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
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
.
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
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.
Que Diriez-vous de
for line in `cat`; do
something($line);
done