Existe-t-il une alternative à la commande «sed -i» dans Solaris?

11

J'ai une exigence dans mon projet de remplacer du texte existant dans un fichier comme foopar un autre texte comme fooofoo:

abc.txt
name
foo
foo1

J'ai donc essayé:

sed -i "s/foo/fooofoo/g" abc.txt

Cependant, j'obtiens cette erreur:

sed: option illégale - i

J'ai trouvé dans le manuel que je dois utiliser:

sed -i\ "s/foo/fooofoo/g" abc.txt

Cependant, cela ne fonctionne pas non plus.

J'ai trouvé des alternatives dans perlet awkaussi mais une solution dans Solaris sedserait très appréciée.

J'utilise cette version de bash:

GNU bash, version 3.2.57 (1) -release (sparc-sun-solaris2.10)

tpsaitwal
la source
Sous Solaris 11 et versions ultérieures, utilisez simplement le /usr/gnu/bin/sedsupport -i.
alanc

Réponses:

15

Utilisez ed. Il est disponible sur la plupart des plates-formes et il peut modifier vos fichiers sur place.
Depuis sedest basé sur edla syntaxe pour le remplacement des modèles est similaire:

ed -s infile <<\IN
,s/old/new/g
w
q
IN
don_crissti
la source
Pourquoi edau lieu de ex, juste curieux? (Je sais que les deux sont conformes à POSIX et que j'ai lié aux spécifications.;))
Wildcard
1
@Wildcard - Je pourrais demander le contraire - pourquoi ex? Pour répondre à votre question: puisque je ne l'utilise jamais, ex je ne la connais pas. btw, j'ai vu des configurations où edétait présent mais expas (mais pas l'inverse) ... le plus notable étant mon ordinateur portable :)
don_crissti
Bonne réponse. :) Ma réponse: Puisque j'utilise Vim tout le temps, je connais très bien les excommandes. Toutes les vicommandes que vous pouvez taper qui commencent par deux points sont des excommandes. Il y a bien sûr quelques extensions spécifiques à Vim, mais juste l'ensemble de commandes de base est incroyablement puissant et flexible et offre de nombreuses modifications par script.
Wildcard
Mon système Manjaro l'a fait exet pourtant pas ed.
mi
8

Si vous ne pouvez pas installer GNU sed, utilisez:

sed "s/foo/fooofoo/g" abc.txt >abc.tmp && mv abc.tmp abc.txt

Cela utilise la redirection pour envoyer la sortie de sed vers un fichier temporaire. Si sed se termine avec succès, cela écrase abc.txtle fichier temporaire.

Comme le montre le code source de GNU sed , c'est exactement ce qui sed -ifait. Donc, c'est à peu près aussi efficace que sed -i.

S'il abc.tmpexiste déjà une chance , vous pouvez utiliser mktempun utilitaire similaire pour générer le nom unique du temporaire.

John1024
la source
Merci pour l'aide. Cependant, y a-t-il une option dans solaris par laquelle nous pouvons mettre à jour le fichier existant sans créer de fichier temporaire?
tpsaitwal
10
Je déteste vous le divulguer, mais même sed crée un fichier temporaire. git.savannah.gnu.org/cgit/sed.git/tree/sed/sed.c#n84
Jeff Schaller
1
Vous pouvez le faire si vous ne vous souciez pas des liens durs - voir mon exemple. Il y a toujours un fichier temporaire, mais il est caché (sans nom). Sans temporaire, vous auriez besoin d'utiliser ed, car il lit tout le fichier en mémoire.
Toby Speight du
3

Si vous voulez l'équivalent de sed -i.bak, c'est assez simple.

Considérez ce script pour GNU sed:

#!/bin/sh

# Create an input file to demonstrate
trap 'rm -r "$dir"' EXIT
dir=$(mktemp -d)
grep -v '[[:upper:][:punct:]]' /usr/share/dict/words | head >"$dir/foo"

# sed program - removes 'aardvark' and 'aardvarks'
script='/aard/d'

##########
# What we want to do
sed -i.bak -e "$script" "$dir"
##########

# Prove that it worked
ls "$dir"
cat "$dir/foo"

Nous pouvons simplement remplacer la ligne marquée par

cp "$dir/foo" "$dir/foo.bak" && sed -e "$script" "$dir/foo.bak" >"$dir/foo"

Cela déplace le fichier existant pour être une sauvegarde et écrit un nouveau fichier.

Si nous voulons l'équivalent de

sed -i -e "$script" "$dir"  # no backup

alors c'est un peu plus complexe. Nous pouvons ouvrir le fichier pour le lire comme entrée standard, puis le dissocier, avant de diriger la sortie de sed pour le remplacer:

( cp "$dir/foo" "$dir/foo.bak"; exec <"$dir/foo.bak"; rm "$dir/foo.bak"; exec sed -e "$script" >"$dir/foo" )

Nous le faisons dans un sous-shell, afin que notre stdin d'origine soit toujours disponible après cela. Il est possible de changer d'entrée et de revenir en arrière sans sous-coque, mais cette façon me semble plus claire.

Notez que nous prenons soin de copier d'abord, plutôt que de créer un nouveau foofichier - cela est important si le fichier est connu sous plusieurs noms (c'est-à-dire avec des liens durs) et que vous voulez être sûr de ne pas rompre les liens .

Toby Speight
la source
3

Tout d'abord, vous devez savoir que la i\commande à laquelle vous faites référence consiste à insérer une ligne de texte, et non à sauvegarder le texte modifié dans le fichier. Il n'y a pas moyen spécifié Posix à utiliser sedpour cela.

Ce que vous pouvez faire est d'utiliser ex, qui est spécifié par POSIX :

printf '%s\n' '%s/find/replace/g' 'x' | ex file.txt

La xcommande est pour "enregistrer et quitter". Vous pouvez également utiliser wqmais xest plus court.

Le %signe au début de la commande de remplacement signifie "Appliquer cette commande à chaque ligne du tampon". Vous pouvez 1,$s/find/replace/gégalement utiliser .


Une différence majeure entre exet sedest qu'il seds'agit d'un éditeur de flux . Il ne fonctionne que séquentiellement, ligne par ligne. exest beaucoup plus flexible que cela, et en fait, vous pouvez directement éditer des fichiers texte interactifs ex. Il est le prédécesseur immédiat de vi.

Caractère générique
la source
1

Utilisation sedet aucun fichier temporaire visible :

Vous pouvez éviter de créer un "fichier temporaire" visible distinct :

exec 3<abc.txt
rm abc.txt
sed 's/foo/fooofoo/' <&3 >abc.txt
exec 3<&-

Explication

Les systèmes Unix ne sont pas réellement supprimer le contenu du fichier à partir du disque jusqu'à ce qu'il soit à la fois dissociées dans le système de fichiers, et pas ouvert dans tout processus. Vous pouvez exec 3<donc ouvrir le fichier dans le shell pour lire sur le descripteur de fichier 3, rmle fichier (qui le dissocie du système de fichiers), puis appeler sedavec le descripteur de fichier 3 utilisé comme entrée.

Notez que cela est très différent de cela:

# Does not work.
sed 's/foo/fooofoo/' <abc.txt >abc.txt

La différence est que lorsque vous le faites dans une seule commande, le shell ouvre simplement le même fichier pour la lecture et pour l'écriture avec l'option de tronquer le fichier - puisqu'il s'agit toujours du même fichier, vous perdez le contenu. Mais si vous l'ouvrez pour la lecture, alors rm, puis ouvrez le même chemin pour l'écriture, vous créez en fait un nouveau fichier au même chemin (mais à un nouvel inode et emplacement de disque, car l'original est toujours ouvert): donc le contenu est toujours disponible.

Ensuite, une fois que vous avez terminé, vous pouvez fermer le descripteur de fichier que vous avez ouvert précédemment (c'est ce que fait la exec 3<&-syntaxe spéciale), qui libère le fichier d'origine afin que le système d'exploitation puisse supprimer (marquer comme inutilisé) son espace disque.

Avertissements

Il y a quelques choses à garder à l'esprit à propos de cette solution:.

  1. Vous n'obtenez qu'un seul "aller" à travers le contenu - il n'y a pas de moyen portable pour un shell de "rechercher" dans le descripteur de fichier - donc une fois qu'un programme lit une partie du contenu, d'autres programmes ne verront que le reste du fichier. Et sedlira l'intégralité du fichier.

  2. Il y a une petite chance que votre fichier d'origine soit perdu si votre shell / script / sed est tué avant la fin.

mtraceur
la source
Pour être clair, @TobySpeight a déjà posté un exemple de cette solution à la fin de sa réponse, mais j'ai pensé qu'elle pourrait utiliser une élaboration plus approfondie.
mtraceur
Pour le downvoter: Bien que je sois sûr que le fait de laisser un downvote vous fait vous sentir bien dans votre peau, j'apprécierais si vous contribuiez davantage en fournissant des commentaires sur les problèmes que vous avez avec cette réponse, comment elle peut être améliorée, etc. .
mtraceur
vous pouvez peut-être utiliser perl -i
c4f4t0r
@ c4f4t0r: Désolé pour la réponse tardive: Oui, perl -ic'est une autre bonne option. La question demandait spécifiquement une solution compatible avec Solaris sed, contrairement aux solutions avec perlet awkqu'ils mentionnaient déjà. De plus, ma réponse visait principalement à ajouter quelque chose que je pensais utile de savoir et qui manquait dans les autres réponses.
mtraceur