Rechercher et remplacer à l'intérieur d'un fichier texte à partir d'une commande Bash

561

Quelle est la manière la plus simple de rechercher et remplacer une chaîne d'entrée donnée, par exemple abc, et de la remplacer par une autre chaîne, par exemple XYZdans un fichier /tmp/file.txt?

J'écris une application et j'utilise IronPython pour exécuter des commandes via SSH - mais je ne connais pas bien Unix et je ne sais pas quoi chercher.

J'ai entendu dire que Bash, en plus d'être une interface de ligne de commande, peut être un langage de script très puissant. Donc, si cela est vrai, je suppose que vous pouvez effectuer des actions comme celles-ci.

Puis-je le faire avec bash, et quel est le script le plus simple (une ligne) pour atteindre mon objectif?

Cendre
la source

Réponses:

930

La façon la plus simple est d'utiliser sed (ou perl):

sed -i -e 's/abc/XYZ/g' /tmp/file.txt

Qui invoquera sed pour effectuer une modification sur place en raison de l' -ioption. Cela peut être appelé depuis bash.

Si vous voulez vraiment vraiment utiliser simplement bash, alors ce qui suit peut fonctionner:

while read a; do
    echo ${a//abc/XYZ}
done < /tmp/file.txt > /tmp/file.txt.t
mv /tmp/file.txt{.t,}

Cela boucle sur chaque ligne, fait une substitution et écrit dans un fichier temporaire (ne veut pas encombrer l'entrée). Le déplacement à la fin se déplace simplement temporairement vers le nom d'origine.

johnny
la source
3
Sauf que l'invocation de mv est à peu près aussi «non Bash» que l'utilisation de sed. J'ai presque dit la même chose de l'écho, mais c'est un shell intégré.
slim
5
L'argument -i pour sed n'existe pas pour Solaris (et je pense que d'autres implémentations) cependant, gardez cela à l'esprit. Je viens de passer plusieurs minutes à comprendre cela ...
Panky
2
Note à soi-même: à propos de l'expression régulière de sed: s/..../..../ - Substituteet /g - Global
somme de contrôle
89
Remarque pour les utilisateurs Mac qui obtiennent une invalid command code Cerreur ... Pour les remplacements sur place, BSD sednécessite une extension de fichier après l' -iindicateur car il enregistre un fichier de sauvegarde avec l'extension donnée. Par exemple: sed -i '.bak' 's/find/replace/' /file.txt Vous pouvez ignorer la sauvegarde en utilisant une chaîne vide comme ceci:sed -i '' 's/find/replace/' /file.txt
Austin
8
Astuce: Si vous souhaitez utiliser une réparation insensible à la casses/abc/XYZ/gi
Boris D. Teoharov
166

La manipulation de fichiers n'est pas normalement effectuée par Bash, mais par des programmes invoqués par Bash, par exemple:

perl -pi -e 's/abc/XYZ/g' /tmp/file.txt

Le -idrapeau lui dit de faire un remplacement sur place.

Voir man perlrunpour plus de détails, y compris comment effectuer une sauvegarde du fichier d'origine.

Alnitak
la source
37
Le puriste en moi dit que vous ne pouvez pas être sûr que Perl sera disponible sur le système. Mais c'est très rarement le cas de nos jours. Je montre peut-être mon âge.
slim
3
Pouvez-vous montrer un exemple plus complexe. Quelque chose comme remplacer "chdir / blah" par "chdir / blah2". J'ai essayé perl -pi -e 's/chdir (?:\\/[\\w\\.\\-]+)+/chdir blah/g' text, mais je reçois toujours une erreur avec Ne pas avoir d'espace entre le motif et le mot suivant est obsolète à la ligne -e 1. Inégalée (en regex; marquée par <- ICI en m / (chdir) () (<- ICI ?: \\ / at -e ligne 1.
CMCDragonkai
@CMCDragonkai Vérifiez cette réponse: stackoverflow.com/a/12061491/2730528
Alfonso Santiago
69

J'ai été surpris quand je suis tombé sur ça ...

Il y a une replacecommande livrée avec le "mysql-server"paquet, donc si vous l'avez installé, essayez-le:

# replace string abc to XYZ in files
replace "abc" "XYZ" -- file.txt file2.txt file3.txt

# or pipe an echo to replace
echo "abcdef" |replace "abc" "XYZ"

Voir man replacepour en savoir plus.

rayro
la source
12
Deux choses sont possibles ici: a) replaceest un outil indépendant utile et les gens de MySQL devraient le libérer séparément et en dépendre b) replacenécessite un peu de MySQL o_O Quoi qu'il en soit, installer mysql-server pour obtenir le remplacement serait la mauvaise chose à faire :)
Philip Whitehouse
ne fonctionne que pour mac? dans mon ubuntu I centos cette commande n'existe pas
paul
1
C'est parce que vous n'avez pas mysql-serverinstallé de package. Comme l'a souligné @rayro, en replacefait partie.
Phius
2
"Avertissement: replace est obsolète et sera supprimé dans une future version."
Steven Vachon
1
Attention à ne pas exécuter la commande REMPLACER sous Windows! Sous Windows, la commande REMPLACER est destinée à une réplication rapide des fichiers. Pas pertinent pour cette discussion.
Maor
53

Il s'agit d'un ancien article, mais pour tous ceux qui souhaitent utiliser des variables comme @centurian a déclaré que les guillemets simples signifient que rien ne sera développé.

Un moyen simple pour obtenir des variables est de faire une concaténation de chaînes puisque cela se fait par juxtaposition dans bash, ce qui suit devrait fonctionner:

sed -i -e "s/$var1/$var2/g" /tmp/file.txt
zcourts
la source
39

Bash, comme d'autres shells, n'est qu'un outil pour coordonner d'autres commandes. En général, vous essayez d'utiliser des commandes UNIX standard, mais vous pouvez bien sûr utiliser Bash pour appeler n'importe quoi, y compris vos propres programmes compilés, d'autres scripts shell, des scripts Python et Perl, etc.

Dans ce cas, il existe deux façons de procéder.

Si vous voulez lire un fichier et l'écrire dans un autre fichier, en faisant une recherche / remplacement au fur et à mesure, utilisez sed:

sed 's/abc/XYZ/g' <infile >outfile

Si vous souhaitez éditer le fichier sur place (comme si vous ouvriez le fichier dans un éditeur, le modifiiez, puis l'enregistriez), fournissez des instructions à l'éditeur de ligne 'ex'

echo "%s/abc/XYZ/g
w
q
" | ex file

Ex est comme vi sans le mode plein écran. Vous pouvez lui donner les mêmes commandes que vous le feriez à l'invite ':' de vi.

svelte
la source
33

J'ai trouvé ce fil entre autres et je suis d'accord qu'il contient les réponses les plus complètes donc j'ajoute le mien aussi:

  1. sedet edsont si utiles ... à la main. Regardez ce code de @Johnny:

    sed -i -e 's/abc/XYZ/g' /tmp/file.txt
  2. Lorsque ma restriction est de l'utiliser dans un script shell, aucune variable ne peut être utilisée à la place de "abc" ou "XYZ". Le BashFAQ semble être d'accord avec ce que je comprends au moins. Donc, je ne peux pas utiliser:

    x='abc'
    y='XYZ'
    sed -i -e 's/$x/$y/g' /tmp/file.txt
    #or,
    sed -i -e "s/$x/$y/g" /tmp/file.txt
    

    Mais que pouvons-nous faire? Comme, @Johnny a dit utiliser un while read...mais, malheureusement, ce n'est pas la fin de l'histoire. Les éléments suivants ont bien fonctionné avec moi:

    #edit user's virtual domain
    result=
    #if nullglob is set then, unset it temporarily
    is_nullglob=$( shopt -s | egrep -i '*nullglob' )
    if [[ is_nullglob ]]; then
       shopt -u nullglob
    fi
    while IFS= read -r line; do
       line="${line//'<servername>'/$server}"
       line="${line//'<serveralias>'/$alias}"
       line="${line//'<user>'/$user}"
       line="${line//'<group>'/$group}"
       result="$result""$line"'\n'
    done < $tmp
    echo -e $result > $tmp
    #if nullglob was set then, re-enable it
    if [[ is_nullglob ]]; then
       shopt -s nullglob
    fi
    #move user's virtual domain to Apache 2 domain directory
    ......
    
  3. Comme on peut voir si nullglobest défini alors, il se comporte étrangement quand il y a une chaîne contenant un *comme dans:

    <VirtualHost *:80>
     ServerName www.example.com
    

    qui devient

    <VirtualHost ServerName www.example.com

    il n'y a pas de support d'angle de fin et Apache2 ne peut même pas se charger.

  4. Ce type d'analyse devrait être plus lent que la recherche et le remplacement en une seule fois, mais, comme vous l'avez déjà vu, il existe quatre variables pour quatre modèles de recherche différents fonctionnant sur un cycle d'analyse.

La solution la plus appropriée à laquelle je peux penser avec les hypothèses données du problème.

centurien
la source
12
Dans votre (2) - vous pouvez le faire sed -e "s/$x/$y/", et cela fonctionnera. Pas les guillemets doubles. Cela peut devenir très déroutant si les chaînes des variables elles-mêmes contiennent des caractères ayant une signification particulière. Par exemple, si x = "/" ou x = "\". Lorsque vous rencontrez ces problèmes, cela signifie probablement que vous devez cesser d'essayer d'utiliser le shell pour ce travail.
slim
Salut mince, je vois que vous êtes également contre l'utilisation de Perl. Quelle est votre solution? Parce que je veux en fait changer dynamiquement un chemin dans un fichier, ce qui signifie que j'ai beaucoup de / dans la chaîne!
Mahdi
20

Vous pouvez utiliser sed:

sed -i 's/abc/XYZ/gi' /tmp/file.txt

Utilisez -ipour "ignorer la casse" si vous n'êtes pas sûr que le texte à trouver soit abcou ABCou AbC, etc.

Vous pouvez utiliser findet sedsi vous ne connaissez pas votre nom de fichier:

find ./ -type f -exec sed -i 's/abc/XYZ/gi' {} \;

Recherchez et remplacez dans tous les fichiers Python:

find ./ -iname "*.py" -type f -exec sed -i 's/abc/XYZ/gi' {} \;
MMParvin
la source
12

Vous pouvez également utiliser la edcommande pour effectuer une recherche dans le fichier et remplacer:

# delete all lines matching foobar 
ed -s test.txt <<< $'g/foobar/d\nw' 

Voir plus dans " Édition de fichiers via des scripts aveced ".

l'homme d'étain
la source
3
Cette solution est indépendante des incompatibilités GNU / FreeBSD (Mac OSX) (contrairement sed -i <pattern> <filename>). Très agréable!
Peterino
11

Si le fichier sur lequel vous travaillez n'est pas si volumineux et que le stockage temporaire dans une variable ne pose aucun problème, vous pouvez utiliser la substitution de chaîne Bash sur l'ensemble du fichier à la fois - il n'est pas nécessaire de le parcourir ligne par ligne:

file_contents=$(</tmp/file.txt)
echo "${file_contents//abc/XYZ}" > /tmp/file.txt

L'ensemble du contenu du fichier sera traité comme une longue chaîne, y compris les sauts de ligne.

XYZ peut être une variable par exemple $replacement, et un avantage de ne pas utiliser sed ici est que vous n'avez pas à vous soucier du fait que la chaîne de recherche ou de remplacement puisse contenir le caractère de délimitation du modèle sed (généralement, mais pas nécessairement, /). Un inconvénient n'est pas de pouvoir utiliser des expressions régulières ou l'une des opérations plus sophistiquées de sed.

johnraff
la source
Des conseils pour l'utiliser avec des tabulations? Pour une raison quelconque, mon script ne trouve rien avec des onglets après être passé de sed avec beaucoup d'échappement à cette méthode.
Brian Hannay
2
Si vous voulez mettre un onglet dans la chaîne que vous remplacez, vous pouvez le faire avec la syntaxe "Bashed single quotes" de Bash, donc un onglet est représenté par $ '\ t', et vous pouvez faire $ echo 'tab' $ '\ t''separated'> testfile; $ file_contents = $ (<testfile); $ echo "$ {file_contents // $ '\ t' / TAB}"; tabTABseparated `
johnraff
5

Essayez la commande shell suivante:

find ./  -type f -name "file*.txt" | xargs sed -i -e 's/abc/xyz/g'
J Ajay
la source
4
C'est une excellente réponse à "comment puis-je accidentellement tous les fichiers dans tous les sous-répertoires aussi", mais cela ne semble pas être ce qui est demandé ici.
tripleee
Cette syntaxe ne fonctionnera pas pour la version BSD de sed, utilisez sed -i''plutôt.
kenorb
5

Pour modifier le texte du fichier de manière non interactive, vous avez besoin d'un éditeur de texte sur place tel que vim.

Voici un exemple simple comment l'utiliser à partir de la ligne de commande:

vim -esnc '%s/foo/bar/g|:wq' file.txt

C'est équivalent à la réponse @slim de l' ancien éditeur qui est fondamentalement la même chose.

Voici quelques exexemples pratiques.

Remplacer le texte foopar bardans le fichier:

ex -s +%s/foo/bar/ge -cwq file.txt

Suppression des espaces de fin pour plusieurs fichiers:

ex +'bufdo!%s/\s\+$//e' -cxa *.txt

Dépannage (lorsque le terminal est bloqué):

  • Ajoutez un -V1paramètre pour afficher les messages détaillés.
  • Force de quitter par: -cwq!.

Voir également:

kenorb
la source
Je voulais faire les remplacements de manière interactive. C'est pourquoi j'ai essayé "vim -esnc '% s / foo / bar / gc |: wq' file.txt". Mais le terminal est bloqué maintenant. Comment effectuer les remplacements de manière interactive sans que le shell bash se comporte bizarrement.
vineeshvs
Pour déboguer, ajouter -V1, pour forcer la fermeture, utilisez wq!.
kenorb
2

Vous pouvez également utiliser python dans le script bash. Je n'ai pas eu beaucoup de succès avec certaines des meilleures réponses ici, et j'ai trouvé que cela fonctionnait sans avoir besoin de boucles:

#!/bin/bash
python
filetosearch = '/home/ubuntu/ip_table.txt'
texttoreplace = 'tcp443'
texttoinsert = 'udp1194'

s = open(filetosearch).read()
s = s.replace(texttoreplace, texttoinsert)
f = open(filetosearch, 'w')
f.write(s)
f.close()
quit()
micalith
la source
1

Vous pouvez utiliser la commande rpl. Par exemple, vous souhaitez modifier le nom de domaine dans l'ensemble du projet php.

rpl -ivRpd -x'.php' 'old.domain.name' 'new.domain.name' ./path_to_your_project_folder/  

Ce n'est pas une cause claire, mais c'est très rapide et utile. :)

zalex
la source