Comment exécuter une commande éditant son fichier (argument) «sur place» en utilisant bash?


110

J'ai un fichier temp.txt, que je veux trier avec la sortcommande en 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 en une seule ligne sans recourir à la copie dans des fichiers temporaires?


EDIT: L' -ooption est très cool pour sort. J'ai utilisé sortdans ma question comme 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?


Réponses:


171
sort temp.txt -o temp.txt

3
Ceci est une réponse. Je me demandais en fait s'il existe une solution générique à ce problème. Par exemple, si je veux trouver toutes les lignes UNIQ dans un fichier "en place", je ne peux pas faire -o
jm.

Ce n'est pas générique, mais vous pouvez utiliser -u avec GNU sort pour trouver des lignes uniques
James

Quelqu'un at-il résolu le problème pour permettre par exemple sort --inplace *.txt? Ce serait cool fou
sehe

@sehe Essayez ceci:find . -name \*.txt -exec sort {} -o {} \;
Keith Gaughan

29

Un a sortbesoin de voir toutes les entrées avant de pouvoir commencer à sortir. Pour cette raison, le sortprogramme peut facilement offrir une option pour modifier un fichier sur place:

sort temp.txt -o temp.txt

Plus précisément, la documentation de GNUsort dit:

Normalement, sort lit toutes les entrées avant d'ouvrir le fichier de sortie, de sorte que vous pouvez trier en toute sécurité un fichier sur place en utilisant des commandes comme sort -o F Fet cat F | sort -o F. Cependant, sortavec --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 - Gn'est pas sûre car le tri peut commencer à écrire Favant d'avoir catfini de le lire.

Alors que la documentation de BSD sortdit:

Si [le] fichier de sortie est l'un des fichiers d'entrée, tri le copie dans un fichier temporaire avant de trier et d'écrire la sortie dans [le] fichier de sortie.

Les commandes telles que uniqpeuvent commencer à écrire la sortie avant de terminer la lecture de 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 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 uniqin 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 le fichier entier, ce n'est pas conseillé lorsque vous travaillez avec des fichiers volumineux.


19

Voici une approche plus générale, fonctionne avec uniq, sort et autres.

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

14
Une autre approche générique, avec spongedes moreutils: cat file |frobnicate |sponge file.
Tobu

3
@Tobu: pourquoi ne pas soumettre cela comme une réponse distincte?
Flimm

1
Il est probablement bon de noter que cela ne préserve pas nécessairement les autorisations des fichiers. Votre umask dicte quelles seront les nouvelles autorisations.
wor

1
Tricky one. Pouvez-vous expliquer comment cela fonctionne exactement?
patryk.beza

2
@ patryk.beza: Dans l'ordre: Le FD d'entrée est ouvert à partir du fichier d'origine; l'entrée de répertoire d'origine est supprimée; la redirection est traitée, créant un nouveau fichier vide avec le même nom que l'ancien; puis la commande s'exécute.
Charles Duffy

10

Le commentaire de Tobu sur l'éponge justifie d'être une réponse à part entière.

Pour citer la page d'accueil de moreutils :

L'outil le plus général de moreutils jusqu'à présent est probablement sponge (1), qui vous permet de faire des choses comme ceci:

% sed "s/root/toor/" /etc/passwd | grep -v joey | sponge /etc/passwd

Cependant, spongesouffre du même problème que Steve Jessop commente ici. Si l'une des commandes du pipeline avant spongeéchoue, le fichier d'origine sera écrasé.

$ mistyped_command my-important-file | sponge my-important-file
mistyped-command: command not found

Oh-oh, my-important-fileest parti.


1
Sponge sait qu'il sera utilisé pour remplacer le fichier d'entrée et il crée initialement un fichier temporaire pour éviter une condition de concurrence. Pour que cela fonctionne, sponge doit être le dernier élément du pipeline et il doit être autorisé à créer le fichier de sortie lui-même (par opposition à la redirection de sortie au niveau du shell, par exemple). BTW: Il semble qu'une solution facile au code source pour le cas «échec» serait de ne pas renommer le fichier temporaire dans le cas d'un pipefail (je ne sais pas pourquoi sponge n'a pas cette option).
Brent Bradburn

Je pense que si vous ajoutez set -o pipefailau début de votre script, l'erreur sur mistyped_command my-important-fileferait la sortie du script immédiatement, avant de s'exécuter sponge, préservant ainsi le fichier important.
Elouan Keryell-Even

6

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
Hm. J'appellerais toujours temp.txt.sort un fichier temporaire.
JesperE

5
Ce code est risqué, car si le tri échoue pour une raison quelconque sans terminer son travail, l'original est écrasé.
Steve Jessop

1
Le manque d'espace disque est une cause plausible ou un signal (l'utilisateur appuie sur CTRL-C).
Steve Jessop

5
si vous voulez utiliser quelque chose comme ça, utilisez && (logique et) au lieu de; car en utilisant cela, vous vous assurez que si une commande échoue, la suivante ne sera pas exécutée. par exemple: cp backup.tar /root/backup.tar && rm backup.tar si vous n'avez pas les droits de copie, vous serez en sécurité car le fichier ne sera pas supprimé
daniels

1
a changé ma réponse pour prendre en compte vos suggestions, merci
davr

4

J'aime le sort file -o file réponse mais je ne veux pas taper deux fois le même nom de fichier.

Utilisation de l'expansion de l'historique BASH :

$ 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 !#$

attrape le dernier argument de la ligne courante.


3

Beaucoup ont mentionné le -o option . 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

Ce serait très contraint 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

Je pense qu'il est possible que >le fichier tronque le fichier avant que la commande ( uniqdans ce cas) ne le lise.
Martin

3

Une alternative aux spongeplus courants sed:

sed -ni r<(command file) file

Il fonctionne pour toute commande ( sort, uniq, tac, ...) et utilise le très bien connu sedd » -ioptions (fichiers modifier en place).

Avertissement: essayez d' command fileabord, car la modification des fichiers sur place n'est pas sûre par nature.


Explication

Tout d' abord, vous dire de sedne pas imprimer les lignes ( d' origine) ( l' -noption ), et avec l'aide de la sed« s rcommande et bash» s Remplacement du processus , 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

1

Utilisez l'argument --output=ou-o

Je viens d'essayer sur FreeBSD:

sort temp.txt -otemp.txt

Bien que correct, il s'agit simplement d'un double de cette réponse
whoan

1

Pour ajouter la uniqcapacité, quels sont les inconvénients à:

sort inputfile | uniq | sort -o inputfile


0

Si vous insistez pour utiliser le sortprogramme, vous devez utiliser un fichier intermédiaire - je ne pense pas qu'il y sortait d'option de tri 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 stdin de sort est suffisamment grande pour contenir le fichier entier.

Edit: honte à moi. sort temp.txt -o temp.txtfonctionne très bien.


J'ai également lu le Q comme étant "en place", mais la deuxième lecture m'a fait croire qu'il ne le demandait pas vraiment
epatel

0

Une autre solution:

uniq file 1<> file

Il convient de noter cependant que l' <>astuce ne fonctionne que dans ce cas car elle uniqest spéciale en ce qu'elle ne copie que les lignes d'entrée sur les lignes de sortie, en laissant tomber certaines en cours de route. Si une autre commande (par exemple sed) a été utilisée qui changerait l'entrée (par exemple changerait tout aen aa), alors elle peut remplacer filed'une manière qui n'a aucun sens et même boucler à l'infini, à condition que l'entrée soit suffisamment grande (plus qu'un tampon de lecture unique).
David
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.