J'ai un fichier temp.txt, que je veux trier avec la commande sort
dans bash.
Je veux 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
Cela peut-il être fait sur une seule ligne sans avoir recours à la copie dans des fichiers temporaires?
EDIT: L'option -o
Est très cool 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?
sort temp.txt -o temp.txt
Un sort
doit voir toutes les entrées avant de pouvoir commencer à 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é en utilisant des commandes comme
sort -o F F
etcat F | sort -o F
. Cependant,sort
avec--merge
(-m
) peut ouvrir le fichier de sortie avant de lire toutes les entrées, donc une commande commecat F | sort -m -o F - G
n'est pas sûr car le tri peut commencer à écrireF
avant quecat
ait fini de le lire.
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.
Des commandes telles que uniq
peuvent commencer à écrire la sortie avant de terminer la lecture de l'entrée. Ces commandes ne prennent généralement pas en charge la modification sur place (et il leur serait plus difficile de prendre en charge cette fonctionnalité).
Vous contournez généralement cela avec un fichier temporaire, ou si vous voulez absolument éviter d'avoir 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 $/; $_ = <>; open(OUT,">temp.txt"); print OUT;'
Ici, la partie Perl lit la sortie complète de uniq
dans la variable $_
puis écrase le fichier d'origine avec ces données. Vous pouvez faire de même dans le langage de script de votre choix, peut-être même dans Bash. Mais notez qu'il aura besoin de suffisamment de mémoire pour stocker l'intégralité du fichier, ce n'est pas conseillé lorsque vous travaillez avec des fichiers volumineux.
Voici une approche plus générale, fonctionne avec uniq, sort et ainsi de suite.
{ rm file && uniq > file; } < file
Commentaire de Tobu sur l'éponge garantit une réponse à part entière.
Pour citer la page d'accueil moreutils :
Jusqu'à présent, l'outil le plus polyvalent de moreutils est l'éponge (1), qui vous permet de faire des choses comme ceci:
% sed "s/root/toor/" /etc/passwd | grep -v joey | sponge /etc/passwd
Cependant, sponge
souffre du même problème Steve Jessop commente ici. Si l'une des commandes du pipeline avant sponge
échoue, le fichier d'origine sera écrit plus de.
$ mistyped_command my-important-file | sponge my-important-file
mistyped-command: command not found
Uh-oh, my-important-file
est parti.
Voilà, une ligne:
sort temp.txt > temp.txt.sort && mv temp.txt.sort temp.txt
Techniquement, il n'y a pas de copie dans un fichier temporaire et la commande 'mv' devrait être instantanée.
J'aime le sort file -o file
répondez mais ne voulez pas taper deux fois le même nom de fichier.
Utiliser BASH expansion de l'historique :
$ sort file -o !#^
saisit le premier argument de la ligne actuelle lorsque vous appuyez sur enter.
Un tri unique en place:
$ sort -u -o file !#$
saisit le dernier argument de la ligne actuelle.
Une alternative à sponge
avec le sed
le plus courant:
sed -ni r<(command file) file
Il fonctionne pour n'importe quelle commande (sort
, uniq
, tac
, ...) et utilise les très connus sed
's -i
Option (modifier les fichiers sur place).
Avertissement: Essayez d'abord command file
Car la modification des fichiers sur place n'est pas sûre par nature.
Tout d'abord, vous dites à sed
de ne pas imprimer les lignes (originales) ( option -n
), et avec l'aide des sed
- r
command et bash
Process Substitution , le contenu généré par <(command file)
sera la sortie enregistrée en place .
Vous pouvez envelopper cette solution dans 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"
}
$ 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
Beaucoup ont mentionné l'option - o. Voici la partie de la page de manuel.
Depuis 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.
Ce serait très limité en 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++] = $0}END{for(j=0;j<i;j++){print line[j]}}' > temp.txt
Lisez sur l'éditeur non interactif, ex
.
Utilisez l'argument --output=
ou -o
Je viens d'essayer sur FreeBSD:
sort temp.txt -otemp.txt
Pour ajouter la capacité uniq
, quels sont les inconvénients de:
sort inputfile | uniq | sort -o inputfile
Si vous insistez pour utiliser le programme sort
, vous devez utiliser un fichier intermédiaire - je ne pense pas que sort
ait une option pour trier en mémoire. Toute autre astuce avec stdin/stdout échouera à moins que vous ne puissiez garantir que la taille de la mémoire tampon pour le stdin du tri est suffisamment grande pour s'adapter à l'ensemble du fichier.
Edit: honte à moi. sort temp.txt -o temp.txt
fonctionne très bien.
Une autre solution:
uniq file 1<> file