sed modifier le fichier en place

300

J'essaie de savoir s'il est possible de modifier un fichier dans une seule commande sed sans diffuser manuellement le contenu modifié dans un nouveau fichier, puis renommer le nouveau fichier en son nom d'origine. J'ai essayé l' -ioption mais mon système Solaris a dit que -ic'était une option illégale. Existe-t-il une manière différente?

amphibient
la source
7
-iest une option dans gnu sed, mais n'est pas dans sed standard. Cependant, il diffuse le contenu dans un nouveau fichier, puis renomme le fichier afin qu'il ne soit pas ce que vous voulez.
William Pursell
2
en fait, c'est ce que je veux, je veux juste ne pas être exposé à devoir effectuer la tâche banale de renommer le nouveau fichier en son nom d'origine
amphibient
3
Ensuite, vous devez reformuler la question.
William Pursell
3
@amphibient: Cela vous dérangerait-il du tout de préfixer le titre de votre question avec le mot "Solaris"? La valeur de votre question se perd. Veuillez voir les commentaires ci-dessous ma réponse. Merci.
Steve
1
@Steve: J'ai de nouveau supprimé le préfixe Solaris du titre, car il n'est en aucun cas exclusif à Solaris.
tripleee

Réponses:

477

L' -ioption diffuse le contenu modifié dans un nouveau fichier, puis le renomme en arrière-plan, de toute façon.

Exemple:

sed -i 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

et

sed -i '' 's/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g' filename

sur macOS .

choroba
la source
3
au moins, il le fait pour moi, donc je n'ai pas à le faire
amphibient
2
Vous pourriez alors essayer de compiler le GNU sed sur votre système.
choroba
3
exemple:sed -i "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>
Thales Ceolin
2
C'est mieux que la solution Perl. D'une manière ou d'une autre, il ne crée aucun fichier .swap .... merci!
mathématiques
3
@maths: Oui, mais peut-être ailleurs ou pour une durée plus courte?
choroba
72

Sur un système où sedn'a pas la possibilité de modifier les fichiers en place, je pense que la meilleure solution serait d'utiliser perl:

perl -pi -e 's/foo/bar/g' file.txt

Bien que cela crée un fichier temporaire, il remplace l'original car un suffixe / extension vide sur place a été fourni.

Steve
la source
14
Non seulement il crée un fichier temporaire, il rompt également les liens durs (par exemple, au lieu de modifier le contenu du fichier, il remplace le fichier par un nouveau fichier du même nom.) Parfois, c'est le comportement souhaité, parfois c'est acceptable, mais lorsqu'il existe plusieurs liens vers un fichier, il s'agit presque toujours du mauvais comportement.
William Pursell
3
@DwightSpencer: Je pense que tous les systèmes sans Perl sont cassés. Une seule commande l'emporte sur une chaîne de commandes lorsque l'on veut des autorisations élevées et que l'on doit l'utiliser sudo. Dans mon esprit, la question est de savoir comment éviter de créer et de renommer manuellement un fichier temporaire sans avoir à installer le dernier GNU ou BSD sed. Utilisez simplement Perl.
Steve
4
@PetrPeller: Vraiment? Si vous lisez attentivement la question, vous comprendrez que l'OP essaie d'éviter quelque chose comme sed 's/foo/bar/g' file.txt > file.tmp && mv file.tmp file.txt. Le fait que l'édition sur place effectue le changement de nom à l'aide d'un fichier temporaire ne signifie pas qu'il doit effectuer un changement de nom manuel lorsque l'option n'est pas disponible. Il existe d'autres outils qui peuvent faire ce qu'il veut, et Perl est le choix évident. Comment n'est-ce pas une réponse à la question d'origine?
Steve
2
@a_arias: Vous avez raison; Il a fait. Mais il ne peut pas atteindre ce qu'il veut en utilisant Solaris sed. La question était en fait un très bon, mais il Apprécions a été diminuée par des gens qui soit ne peuvent pas lire les pages de manuel ou ne peut pas être pris la peine de réellement des questions lues ici sur le SO.
Steve
4
@EdwardGarson: IIRC, Solaris est livré avec Perl mais pas Python ou Ruby. Et comme je l'ai déjà dit, l'OP ne peut pas atteindre le résultat souhaité en utilisant Solaris sed.
Steve
56

Notez que sur OS X, vous pouvez obtenir des erreurs étranges comme "code de commande invalide" ou d'autres erreurs étranges lors de l'exécution de cette commande. Pour résoudre ce problème, essayez

sed -i '' -e "s/STRING_TO_REPLACE/STRING_TO_REPLACE_IT/g" <file>

En effet, sur la version OSX de sed, l' -ioption attend un extensionargument, de sorte que votre commande est en fait analysée comme extensionargument et que le chemin d'accès au fichier est interprété comme le code de commande. Source: https://stackoverflow.com/a/19457213

diadyne
la source
1
C'est une autre bizarrerie d'OS X. Heureusement, un brew install gnu-sedavec un PATHvous permettra d'utiliser la version GNU de sed. Merci d'avoir mentionné; un gratte-tête précis.
Doug
23

Ce qui suit fonctionne bien sur mon mac

sed -i.bak 's/foo/bar/g' sample

Nous remplaçons foo par bar dans le fichier d'exemple. La sauvegarde du fichier d'origine sera enregistrée dans sample.bak

Pour modifier en ligne sans sauvegarde, utilisez la commande suivante

sed -i'' 's/foo/bar/g' sample
minhas23
la source
Comment empêcher la conservation des fichiers de sauvegarde?
Petr Peller
@PetrPeller, vous pouvez utiliser la commande mentionnée pour le même ... sed -i '' 's / foo / bar / g' échantillon
minhas23
18

Une chose à noter, sedne peut pas écrire des fichiers seul car le seul but de sed est d'agir en tant qu'éditeur sur le "flux" (c'est-à-dire les pipelines de stdin, stdout, stderr et d'autres >&ntampons, sockets et similaires). Dans cet esprit, vous pouvez utiliser une autre commande teepour réécrire la sortie dans le fichier. Une autre option consiste à créer un patch à partir de la canalisation du contenu diff.

Méthode des tés

sed '/regex/' <file> | tee <file>

Méthode de patch

sed '/regex/' <file> | diff -p <file> /dev/stdin | patch

METTRE À JOUR:

Notez également que le correctif entraînera la modification du fichier à partir de la ligne 1 de la sortie diff:

Le patch n'a pas besoin de savoir à quel fichier accéder car il se trouve dans la première ligne de la sortie de diff:

$ echo foobar | tee fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin
*** fubar   2014-03-15 18:06:09.000000000 -0500
--- /dev/stdin  2014-03-15 18:06:41.000000000 -0500
***************
*** 1 ****
! foobar
--- 1 ----
! fubar

$ sed 's/oo/u/' fubar | diff -p fubar /dev/stdin | patch
patching file fubar
Dwight Spencer
la source
2
/dev/stdin 2014-03-15, a répondu le 7 janvier - êtes-vous un voyageur dans le temps?
Adrian Frühwirth
Sur Windows, en utilisant msysgit, /dev/stdinn'existe pas, vous devez donc remplacer /dev/stdinpar '-', un seul trait d'union sans les guillemets, donc les commandes suivantes devraient fonctionner: $ sed 's/oo/u/' fubar | diff -p fubar -et$ sed 's/oo/u/' fubar | diff -p fubar - | patch
Jim Raden
@ AdrianFrühwirth J'ai une boîte de police bleue quelque part et pour une raison quelconque, ils m'appellent le docteur au travail. Mais honnêtement, il semble qu'il y ait vraiment une mauvaise dérive d'horloge sur le système à l'époque.
Dwight Spencer
Ces solutions me paraissent dépendre d'un comportement de mise en mémoire tampon particulier. Je soupçonne que pour les fichiers plus gros, ceux-ci peuvent se casser car pendant que sed lit, le fichier peut commencer à changer en dessous par la commande patch, ou pire encore par la commande tee qui tronquera le fichier. En fait, je ne sais pas si patchcela ne sera pas tronqué aussi. Je pense que l'enregistrement de la sortie dans un autre fichier, puis la catsaisie dans l'original serait plus sûr.
akostadinov
vraie réponse sur place de @ william-pursell
akostadinov
11

Il n'est pas possible de faire ce que vous voulez sed. Même les versions sedqui prennent en charge l' -ioption de modification d'un fichier sur place font exactement ce que vous avez explicitement déclaré que vous ne voulez pas: elles écrivent dans un fichier temporaire puis renomment le fichier. Mais vous pouvez peut-être simplement utiliser ed. Par exemple, pour changer toutes les occurrences de fooen bardans le fichier file.txt, vous pouvez faire:

echo ',s/foo/bar/g; w' | tr \; '\012' | ed -s file.txt

La syntaxe est similaire sed, mais certainement pas exactement la même.

Mais vous devriez peut-être vous demander pourquoi vous ne voulez pas utiliser un fichier temporaire renommé. Même si vous n'avez pas de -isupport sed, vous pouvez facilement écrire un script pour faire le travail pour vous. Au lieu de cela sed -i 's/foo/bar/g' file, vous pourriez le faire inline file sed 's/foo/bar/g'. Un tel script est trivial à écrire. Par exemple:

#!/bin/sh -e
IN=$1
shift
trap 'rm -f $tmp' 0
tmp=$( mktemp )
<$IN "$@" >$tmp && cat $tmp > $IN  # preserve hard links

devrait convenir à la plupart des utilisations.

William Pursell
la source
1
ainsi, par exemple, comment nommeriez-vous ce script et comment l'appelleriez-vous?
amphibie du
2
Je l'appellerais inlineet l'invoquerais comme décrit ci-dessus:inline inputfile sed 's/foo/bar/g'
William Pursell
1
wow, edseule réponse qui fait vraiment sur place. C'est la première fois que j'en ai besoin ed. Je vous remercie. Une petite optimisation consiste à utiliser echo -e ",s/foo/bar/g\012 w"ou echo $',s/foo/bar/g\012 w'lorsque le shell lui permet d'éviter un trappel supplémentaire .
akostadinov
2
edne fait pas vraiment les modifications sur place. À partir de la stracesortie, GNU edcrée un fichier temporaire, écrit les modifications dans le fichier temporaire, puis réécrit l'intégralité du fichier d'origine, en préservant les liens durs. À partir de la trusssortie, Solaris 11 edutilise également un fichier temporaire, mais renomme le fichier temporaire en nom de fichier d'origine lors de l'enregistrement avec la wcommande, détruisant tous les liens matériels.
Andrew Henle
@AndrewHenle C'est décourageant. Celui edqui est livré avec les macos actuelles (Mojave, quel que soit le jargon marketing) conserve toujours les liens physiques. YMMV
William Pursell
10

Vous pouvez utiliser vi

vi -c '%s/foo/bar/g' my.txt -c 'wq'
mench
la source
9

sed prend en charge l'édition sur place. De man sed:

-i[SUFFIX], --in-place[=SUFFIX]

    edit files in place (makes backup if extension supplied)

Exemple :

Disons que vous avez un fichier hello.txtavec le texte:

hello world!

Si vous souhaitez conserver une sauvegarde de l'ancien fichier, utilisez:

sed -i.bak 's/hello/bonjour' hello.txt

Vous vous retrouverez avec deux fichiers: hello.txtavec le contenu:

bonjour world!

et hello.txt.bakavec l'ancien contenu.

Si vous ne souhaitez pas conserver de copie, ne passez pas le paramètre d'extension.

Sergio A.
la source
3
L'OP mentionne spécifiquement que sa plate-forme ne prend pas en charge cette option (populaire mais) non standard.
tripleee
3

Vous n'avez pas spécifié quel shell vous utilisez, mais avec zsh vous pouvez utiliser la =( )construction pour y parvenir. Quelque chose dans le sens de:

cp =(sed ... file; sync) file

=( )est similaire à >( )mais crée un fichier temporaire qui est automatiquement supprimé à la cpfin.

Thor
la source
3
mv file.txt file.tmp && sed 's/foo/bar/g' < file.tmp > file.txt

Devrait conserver tous les liens physiques, car la sortie est redirigée pour écraser le contenu du fichier d'origine, et évite d'avoir besoin d'une version spéciale de sed.

JJM
la source
3

Si vous remplacez le même nombre de caractères et après avoir lu attentivement l' édition «sur place» des fichiers ...

Vous pouvez également utiliser l'opérateur de redirection <>pour ouvrir le fichier en lecture et en écriture:

sed 's/foo/bar/g' file 1<> file

Voir en direct:

$ cat file
hello
i am here                           # see "here"
$ sed 's/here/away/' file 1<> file  # Run the `sed` command
$ cat file
hello
i am away                           # this line is changed now

Depuis le manuel de référence de Bash → 3.6.10 Ouverture de descripteurs de fichiers pour la lecture et l'écriture :

L'opérateur de redirection

[n]<>word

entraîne l'ouverture du fichier dont le nom est l'expansion du mot pour la lecture et l'écriture sur le descripteur de fichier n, ou sur le descripteur de fichier 0 si n n'est pas spécifié. Si le fichier n'existe pas, il est créé.

fedorqui 'SO arrête de nuire'
la source
Ce fichier est corrompu. Je ne suis pas sûr de la raison derrière cela. paste.fedoraproject.org/462017
Nehal J Wani
Voir lignes [43-44], [88-89], [135-136]
Nehal J Wani
2
Cet article de blog explique pourquoi cette réponse ne doit pas être utilisée. backreference.org/2011/01/29/in-place-editing-of-files
Nehal J Wani
@NehalJWani Je vais avoir une longue lecture de l'article, merci pour le heads-up! Ce que je n'ai pas mentionné dans la réponse, c'est que cela fonctionne si cela change le même nombre de caractères.
fedorqui 'SO arrête de nuire'
@NehalJWani ceci étant dit, je suis désolé si ma réponse a causé du tort à vos fichiers. Je vais le mettre à jour avec des corrections (ou le supprimer si nécessaire) après avoir lu les liens que vous avez fournis.
fedorqui 'SO arrête de nuire'
2

Comme Moneypenny l'a dit dans Skyfall: "Parfois, les anciennes méthodes sont les meilleures." Kincade a dit quelque chose de similaire plus tard.

$ printf ',s/false/true/g\nw\n' | ed {YourFileHere}

Bonne retouche en place. Ajout de \ nw \ n pour écrire le fichier. Toutes mes excuses pour la réponse tardive à la demande.

jlettvin
la source
Pouvez-vous expliquer ce que cela fait?
Matt Montag
1
Il appelle ed (1), l'éditeur de texte avec quelques caractères écrits dans stdin. En tant que personne de moins de 30 ans, je pense que je peux dire que ed (1) est pour les dinosaures comme les dinosaures le sont pour nous. Quoi qu'il en soit, au lieu d'ouvrir ed et de taper ce genre de choses, jlettvin redirige les personnages avec |. @MattMontag
Ari Sweedler
Si vous demandez ce que font les commandes, il y en a deux, terminées par un \n. [range] s / <old> / <new> / <flag> est la forme de la commande de substitution - un paradigme encore présent dans de nombreux autres éditeurs de texte (okay, vim, un descendant direct de ed) Quoi qu'il en soit, le gdrapeau signifie "global", et signifie "chaque instance sur chaque ligne que vous regardez". La plage 3,5examine les lignes 3-5. ,5examine StartOfFile-5, 3,examine les lignes 3-EndOfFile et ,examine StartOfFile-EndOfFile. Une commande de substitution globale sur chaque ligne qui remplace "false" par "true". Ensuite, la commande d'écriture,, west entrée.
Ari Sweedler
1

De très bons exemples. J'ai eu le défi de modifier sur place de nombreux fichiers et l'option -i semble être la seule solution raisonnable à l'utiliser dans la commande find. Voici le script pour ajouter "version:" devant la première ligne de chaque fichier:

find . -name pkg.json -print -exec sed -i '.bak' '1 s/^/version /' {} \;
Robert
la source