web-dev-qa-db-fra.com

Comment exécuter une commande modifiant son fichier (argument) "sur place" à l'aide de bash?

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?

108
jm.
sort temp.txt -o temp.txt
171
daniels

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 et cat 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 comme cat F | sort -m -o F - G n'est pas sûr car le tri peut commencer à écrire F avant que cat 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.

29
Bruno De Fraine

Voici une approche plus générale, fonctionne avec uniq, sort et ainsi de suite.

{ rm file && uniq > file; } < file
19
wor

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.

9
Sean

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.

6
davr

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.

4
johnnyB

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.


Explication

Tout d'abord, vous dites à sed de ne pas imprimer les lignes (originales) ( option -n ), et avec l'aide des sed - r command et bashProcess Substitution , le contenu généré par <(command file) sera la sortie enregistrée en place .


Rendre les choses encore plus faciles

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"
}

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
3
whoan

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.
3
epatel

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
3
JayG

Lisez sur l'éditeur non interactif, ex .

1
slim

Utilisez l'argument --output= ou -o

Je viens d'essayer sur FreeBSD:

sort temp.txt -otemp.txt
1
sammyo

Pour ajouter la capacité uniq, quels sont les inconvénients de:

sort inputfile | uniq | sort -o inputfile
1
jasper

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.

0
JesperE

Une autre solution:

uniq file 1<> file
0
Antonio Lebrón