En Perl, le code suivant sera lu dans le fichier spécifié dans les arguments de la ligne de commande ou dans stdin:
while (<>) {
print($_);
}
C'est très pratique. Je veux juste savoir quel est le moyen le plus simple de lire un fichier ou stdin en bash.
La solution suivante lit dans un fichier si le script est appelé Avec un nom de fichier comme premier paramètre $1
, sinon à partir de l'entrée standard.
while read line
do
echo "$line"
done < "${1:-/dev/stdin}"
La substitution ${1:-...}
prend $1
si défini sinon Le nom de fichier de l'entrée standard du processus propre est utilisé.
La solution la plus simple est peut-être de rediriger stdin avec un opérateur de redirection de fusion:
#!/bin/bash
less <&0
Stdin est le descripteur de fichier zéro. Ce qui précède envoie les entrées vers votre script bash dans le stdin de less.
En savoir plus sur la redirection de descripteur de fichier .
Voici le moyen le plus simple:
#!/bin/sh
cat -
Usage:
$ echo test | sh my_script.sh
test
Pour affecter stdin à la variable, vous pouvez utiliser: STDIN=$(cat -)
ou tout simplement STDIN=$(cat)
car l'opérateur n'est pas nécessaire (selon @ mklement0 comment ).
Pour analyser chaque ligne de l'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 aucun argument n'est 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")
Remarques:
-
read -r
- Ne traite pas un caractère barre oblique inversée de manière particulière. Considérez chaque barre oblique inverse comme faisant partie de la ligne de saisie.- Sans définir
IFS
, par défaut les séquences de Space et Tab au début et à la fin des lignes sont ignorés (ajustés).- Utilisez
printf
au lieu deecho
pour éviter d’imprimer des lignes vides lorsque la ligne est constituée d’un seul-e
,-n
ou-E
. Cependant, il existe une solution de contournement en utilisantenv POSIXLY_CORRECT=1 echo "$line"
qui exécute votre external GNUecho
qui le prend en charge. Voir: Comment puis-je faire écho à "-e"?
Voir: Comment lire stdin quand aucun argument n'est passé? à stackoverflow SE
Je pense que c'est le moyen le plus simple:
$ 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 solution echo
ajoute de nouvelles lignes chaque fois que IFS
interrompt le flux d'entrée. La réponse de @ fgm peut être modifiée un peu:
cat "${1:-/dev/stdin}" > "${2:-/dev/stdout}"
La boucle Perl de la question lit dans all les arguments du nom de fichier sur la ligne de commande ou dans l'entrée standard si aucun fichier n'est spécifié. Les réponses que je vois semblent toutes traiter un seul fichier ou une entrée standard si aucun fichier n'est spécifié.
Bien que souvent dérisoire avec précision comme UUOC (Utilisation inutile de cat
), il est des moments où cat
est le meilleur outil de travail, et on peut soutenir que c’est l’un d’eux:
cat "$@" |
while read -r line
do
echo "$line"
done
Le seul inconvénient est qu’il crée un pipeline s’exécutant dans un sous-shell. Ainsi, des tâches telles que les affectations de variables dans la boucle while
ne sont pas accessibles en dehors du pipeline. La solution bash
est la suivante: Process Substitution :
while read -r line
do
echo "$line"
done < <(cat "$@")
Cela laisse la boucle while
en cours d'exécution dans le shell principal. Les variables définies dans la boucle sont donc accessibles en dehors de la boucle.
Le comportement de Perl, avec le code indiqué dans l'OP, ne peut prendre aucun ou plusieurs arguments, et si un argument est un seul trait d'union -
, il est compris comme étant stdin. De plus, il est toujours possible d'avoir le nom de fichier avec $ARGV
. Aucune des réponses données jusqu'à présent ne reproduit vraiment le comportement de Perl à cet égard. Voici une possibilité pure Bash. L'astuce consiste à utiliser exec
de manière appropriée.
#!/bin/bash
(($#)) || set -- -
while (($#)); do
{ [[ $1 = - ]] || exec < "$1"; } &&
while read -r; do
printf '%s\n' "$REPLY"
done
shift
done
Le nom de fichier est disponible dans $1
.
Si aucun argument n'est fourni, nous avons artificiellement défini -
en tant que premier paramètre de position. Nous bouclons 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, nous bouclons 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 suit (à moins bien sûr que vous ne le vouliez pas et que vous sachiez 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
S'il vous plaît essayez le code suivant:
while IFS= read -r line; do
echo "$line"
done < file
Le code ${1:-/dev/stdin}
comprendra simplement le premier argument, alors, que diriez-vous de cela.
ARGS='$*'
if [ -z "$*" ]; then
ARGS='-'
fi
eval "cat -- $ARGS" | while read line
do
echo "$line"
done
J'ai combiné toutes les réponses ci-dessus et créé une fonction Shell qui conviendrait à mes besoins. Cela 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é, alors je dois générer un nom de fichier temporaire ayant l'heure, les minutes et les secondes. Le dossier principal partagé contient les sous-dossiers des jours de la semaine. Ceci est à des fins d'organisation.
Voici le script 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 $1 ] && cp $1 //$OTHER/stargate/$(date +'%a')/ || echo "cannot read file"
fi
}
Je voudrais savoir s’il est possible de l’optimiser davantage.
Ce qui suit fonctionne avec la variable standard sh
(testée avec dash
sur Debian) et est assez lisible, mais c’est une question de goût:
if [ -n "$1" ]; then
cat "$1"
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. Ensuite, la sortie de l'instruction if
entière est traitée par le commands_and_transformations
.
Je ne trouve aucune de ces réponses acceptables. En particulier, la réponse acceptée ne gère que le premier paramètre de ligne de commande et ignore le reste. Le programme Perl qu'il essaie d'émuler gère tous les paramètres de 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 d'écho d'une entrée à l'autre ou sont simplement inutilement compliquées.
Cependant, je dois leur donner un peu de crédit car ils m'ont donné des 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