On pourrait penser que
echo foo >a
cat a | rev >a
laisserait a
contenant oof
; mais à la place, il est laissé vide.
rev
à a
?Il y a une application pour ça! La commande sponge
de moreutils
est conçue précisément pour cela. Si vous utilisez Linux, il est probablement déjà installé, sinon recherchez les référentiels de votre système d'exploitation pour sponge
ou moreutils
. Ensuite, vous pouvez faire:
echo foo >a
cat a | rev | sponge a
Ou, en évitant le oC :
rev a | sponge a
La raison de ce comportement est liée à l'ordre dans lequel vos commandes sont exécutées. Le > a
est en fait la toute première chose exécutée et > file
vide le fichier. Par exemple:
$ echo "foo" > file
$ cat file
foo
$ > file
$ cat file
$
Ainsi, lorsque vous exécutez cat a | rev >a
ce qui se passe réellement, c'est que le > a
est exécuté en premier, vidant le fichier, donc quand cat a
est exécuté le fichier est déjà vide. C'est précisément pourquoi sponge
a été écrit (à partir de man sponge
, c'est moi qui souligne):
l'éponge lit l'entrée standard et l'écrit dans le fichier spécifié. Contrairement à une redirection Shell, l'éponge absorbe toutes ses entrées avant d'écrire le fichier de sortie. Cela permet de construire des pipelines qui lisent et écrivent dans le même fichier.
une autre façon de résoudre ce problème consiste à utiliser une méthode d'écriture qui ne tronque pas
rev a | dd conv=notrunc of=a
cela ne fonctionne que parce que:
rev lit le contenu avant de produire la sortie et la sortie n'est jamais plus longue que la quantité déjà lue
le nouveau contenu du fichier est de la même taille ou plus grand que l'original (dans ce cas, même taille)
Cette approche peut être utile pour la modification sur place de fichiers trop volumineux pour conserver des copies temporaires de.
cat a | rev > a
Pourquoi [est
a
laissé vide]?
Dans le pipeline ci-dessus, le shell bifurque deux sous-processus, un pour chacune des deux parties du pipeline. Ces sous-processus exécutent ensuite les commandes en question, traitant d'abord toutes les redirections, puis appelant l'une des fonctions exec*()
pour démarrer l'utilitaire externe. Les sous-processus s'exécutent en parallèle, et il n'y a aucune garantie de synchronisation entre eux.
L'exécution d'un processus n'est pas très rapide, donc ce qui se passe généralement, c'est que le Shell sur le côté droit parvient à configurer la redirection avant que cat
n'ait la possibilité de lire le fichier. La redirection de sortie > a
Tronque le fichier, donc cat
n'a rien à lire, rev
ne reçoit aucune donnée et ne produit aucune donnée. Même si vous avez également utilisé une redirection du côté gauche (cat < a | rev > a
), a
pourrait être ouvert en lecture avant d'être tronqué, mais cat
n'aurait probablement pas le temps de le lire avant cela.
D'un autre côté, cela imprime de manière assez constante a contains: foo
Sur mon système:
echo foo > a; cat < a | tee a > /dev/null ; echo "a contains: $(cat a)"
Ici, c'est tee
qui tronque le fichier, donc cela se produit après que la exec()
et cat
a une meilleure chance d'avoir le temps de lire le fichier. Mais si le fichier était suffisamment volumineux, il pourrait être tronqué au milieu de sa lecture.
J'ai dit que pourrait et probablement là-bas, car en effet l'exact opposé peut se produire , si le système d'exploitation décide de planifier les processus d'une autre manière.
Comment appliquer autrement
rev
àa
?
La solution habituelle consiste à utiliser un fichier temporaire:
cat a | rev > b && mv b a
Bien qu'il y ait le problème habituel de remplacer éventuellement un fichier existant, sauf si vous pouvez être sûr que le nom du fichier temporaire est disponible. Vous devriez probablement utiliser mktemp
:
f=$(mktemp ./tmp.XXXXXX)
cat a | rev > "$f" && mv "$f" a || rm "$f"
Alternativement, vous pouvez utiliser l'outil sponge
, qui s'assure de lire toutes les entrées qu'il obtient avant d'ouvrir le fichier de sortie (sinon c'est comme cat
):
cat a | rev | sponge a
ou juste
rev < a | sponge a
sponge > a
Serait une erreur pour la même raison que la commande d'origine ne fonctionne pas.
L'éponge vient de moreutils , et n'est pas un outil standard. Certaines alternatives sont listées dans Tamponner complètement la sortie de la commande avant de passer à une autre commande?
Certains utilitaires peuvent implémenter eux-mêmes une fonctionnalité similaire, par exemple sort -o outputfile
Ouvre le fichier de sortie seulement après la fin, voir Est-ce que le tri prend en charge le tri d'un fichier sur place, comme `sed --in-place`?
Vous pouvez utiliser Vim en mode Ex:
ex -s -c '%!rev' -c x a.txt
%
sélectionner toutes les lignes!
exécuter la commandex
enregistrer et fermer