Comment exécuter une commande modifiant son fichier (argument) & # 8220; à la place & # 8221; en utilisant bash?
-
02-07-2019 - |
Question
J'ai un fichier temp.txt que je souhaite trier avec la commande sort
dans bash.
Je souhaite que les résultats triés remplacent le fichier d'origine.
Cela ne fonctionne pas par exemple (j'obtiens un fichier vide):
sortx temp.txt > temp.txt
Peut-on faire cela en une seule ligne sans avoir recours à la copie dans des fichiers temporaires?
EDIT: L'option -o
est très pratique pour sort
. J'ai utilisé sort
dans ma question à titre d'exemple. Je rencontre le même problème avec d’autres commandes:
uniq temp.txt > temp.txt.
Existe-t-il une meilleure solution générale?
La solution
sort temp.txt -o temp.txt
Autres conseils
Un tri
doit voir toutes les entrées avant de pouvoir sortir. Pour cette raison, le programme sort
peut facilement offrir une option pour modifier un fichier sur place:
sort temp.txt -o temp.txt
Plus précisément, la documentation de GNU sort
dit:
Normalement, sort lit toutes les entrées avant d'ouvrir le fichier de sortie. Vous pouvez donc trier un fichier en toute sécurité à l'aide de commandes telles que
sort -o F F
etcat F | trier -o F
. Cependant,sort
avec- fusion
(-m
) peut ouvrir le fichier de sortie avant de lire toutes les entrées. Une commande telle quecat F | sort -m -o F - G
n'est pas sans danger car sort pourrait commencer à écrireF
avant quecat
ne soit terminé.
Alors que la documentation de BSD sort
dit:
LesSi [le] fichier de sortie est l'un des fichiers d'entrée, sort le copie dans un fichier temporaire avant de trier et d'écrire la sortie dans [le] fichier de sortie.
commandes telles que uniq
peuvent commencer à écrire la sortie avant d'avoir fini de lire l'entrée. Ces commandes ne prennent généralement pas en charge l’édition sur place (et il leur serait plus difficile de prendre en charge cette fonctionnalité).
Vous travaillez généralement autour de cela avec un fichier temporaire ou, si vous voulez absolument éviter de créer un fichier intermédiaire, vous pouvez utiliser un tampon pour stocker le résultat complet avant de l'écrire. Par exemple, avec perl
:
uniq temp.txt | perl -e 'undef $/; Un tri
doit voir toutes les entrées avant de pouvoir sortir. Pour cette raison, le programme sort
peut facilement offrir une option pour modifier un fichier sur place:
sort temp.txt -o temp.txt
Plus précisément, la documentation de GNU sort
dit:
Normalement, sort lit toutes les entrées avant d'ouvrir le fichier de sortie. Vous pouvez donc trier un fichier en toute sécurité à l'aide de commandes telles que sort -o F F
et cat F | trier -o F
. Cependant, sort
avec - fusion
( -m
) peut ouvrir le fichier de sortie avant de lire toutes les entrées. Une commande telle que cat F | sort -m -o F - G
n'est pas sans danger car sort pourrait commencer à écrire F
avant que cat
ne soit terminé.
Alors que la documentation de BSD sort
dit:
Si [le] fichier de sortie est l'un des fichiers d'entrée, sort le copie dans un fichier temporaire avant de trier et d'écrire la sortie dans [le] fichier de sortie.
Les commandes telles que uniq
peuvent commencer à écrire la sortie avant d'avoir fini de lire l'entrée. Ces commandes ne prennent généralement pas en charge l’édition sur place (et il leur serait plus difficile de prendre en charge cette fonctionnalité).
Vous travaillez généralement autour de cela avec un fichier temporaire ou, si vous voulez absolument éviter de créer un fichier intermédiaire, vous pouvez utiliser un tampon pour stocker le résultat complet avant de l'écrire. Par exemple, avec perl
:
<*>
Ici, la partie perl lit la sortie complète de uniq
dans la variable $ _
, puis remplace le fichier d'origine par ces données. Vous pouvez faire la même chose dans le langage de script de votre choix, peut-être même en bash. Notez toutefois que vous aurez besoin de suffisamment de mémoire pour stocker l’ensemble du fichier, ce qui n’est pas conseillé lorsque vous travaillez avec des fichiers volumineux.
= <>; open(OUT,">temp.txt"); print OUT;'
Ici, la partie perl lit la sortie complète de uniq
dans la variable $ _
, puis remplace le fichier d'origine par ces données. Vous pouvez faire la même chose dans le langage de script de votre choix, peut-être même en bash. Notez toutefois que vous aurez besoin de suffisamment de mémoire pour stocker l’ensemble du fichier, ce qui n’est pas conseillé lorsque vous travaillez avec des fichiers volumineux.
Voici une approche plus générale, fonctionne avec uniq, sort et tout le reste.
{ rm file && uniq > file; } < file
Le commentaire de Tobu sur Sponge donne à être une réponse à part entière.
Citation de la page d'accueil moreutils :
L’outil le plus couramment utilisé à moreutils jusqu’à présent est probablement sponge (1), qui vous permet de faire des choses comme celle-ci:
% sed "s/root/toor/" /etc/passwd | grep -v joey | sponge /etc/passwd
Cependant, sponge
souffre du même problème Steve Jessop commente ici. S'il y en a un. des commandes dans le pipeline avant que sponge
échoue, le fichier d'origine sera écrasé.
$ mistyped_command my-important-file | sponge my-important-file
mistyped-command: command not found
Uh-oh, mon-fichier-important
est parti.
Voilà, une ligne:
sort temp.txt > temp.txt.sort && mv temp.txt.sort temp.txt
Techniquement, il n’ya pas de copie dans un fichier temporaire et la commande "mv" doit être instantanée.
J'aime la réponse tri fichier -o fichier
mais je ne souhaite pas taper le même nom de fichier deux fois.
Utilisation de BASH extension de l'historique :
$ sort file -o !#^
saisit le premier argument de la ligne en cours lorsque vous appuyez sur enter .
Un tri unique sur place:
$ sort -u -o file !#$
récupère le dernier argument de la ligne en cours.
Beaucoup ont mentionné l'option -o . Voici la partie de la page de manuel.
À partir de la page de manuel:
-o output-file
Write output to output-file instead of to the standard output.
If output-file is one of the input files, sort copies it to a
temporary file before sorting and writing the output to output-
file.
Cela serait fortement contraint par la mémoire, mais vous pouvez utiliser awk pour stocker les données intermédiaires en mémoire, puis les réécrire.
uniq temp.txt | awk '{line[i++] = <*>}END{for(j=0;j<i;j++){print line[j]}}' > temp.txt
Alternative à éponge
avec le sed
plus commun:
sed -ni r<(command file) file
Cela fonctionne pour n'importe quelle commande ( sort
, uniq
, tac
, ...) et utilise le très connu sed L'option
de (éditer les fichiers sur place). -i
de
Avertissement: essayez d'abord le fichier de commande
car la modification des fichiers sur place n'est pas sûre par nature.
Explication
Tout d'abord, vous dites à sed
de ne pas imprimer les lignes (originales) ( -n
), et à l'aide du sed
de commande r
et bash
de Substitution de processus , le contenu généré par < (fichier de commande)
sera la sortie enregistrée en place .
Rendre les choses encore plus faciles
Vous pouvez intégrer cette solution à une fonction:
ip_cmd() { # in place command
CMD=${1:?You must specify a command}
FILE=${2:?You must specify a file}
sed -ni r<("$CMD" "$FILE") "$FILE"
}
Exemple
$ cat file
d
b
c
b
a
$ ip_cmd sort file
$ cat file
a
b
b
c
d
$ ip_cmd uniq file
$ cat file
a
b
c
d
$ ip_cmd tac file
$ cat file
d
c
b
a
$ ip_cmd
bash: 1: You must specify a command
$ ip_cmd uniq
bash: 2: You must specify a file
Utilisez l'argument - output =
ou -o
J'ai juste essayé FreeBSD:
sort temp.txt -otemp.txt
Pour ajouter la fonctionnalité uniq
, quels sont les inconvénients de:
sort inputfile | uniq | sort -o inputfile
Consultez l'éditeur non interactif, ex
.
Si vous insistez pour utiliser le programme sort
, vous devez utiliser un fichier intermédiaire. Je ne pense pas que sort
propose une option de tri en mémoire. Toute autre astuce avec stdin / stdout échouera sauf si vous pouvez garantir que la taille de la mémoire tampon pour le tri de stdin est suffisamment grande pour contenir tout le fichier.
Edit: honte à moi. sort temp.txt -o temp.txt
fonctionne parfaitement.
Une autre solution:
uniq file 1<> file