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

Quelle est la manière la plus simple de faire une recherche et un remplacement pour une chaîne d’entrée donnée, disons abc , et remplacez-la par une autre chaîne, par exemple XYZ dans le fichier /tmp/file.txt ?

J’écris une application et utilise IronPython pour exécuter des commandes via SSH – mais je ne connais pas très 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 objective?

Le 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 grâce à l’option -i . Cela peut être appelé depuis bash.

Si vous voulez 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 fait une boucle sur chaque ligne, effectue une substitution et écrit dans un fichier temporaire (ne veut pas décaper l’entrée). Le mouvement à la fin déplace simplement temporairement le nom d’origine.

La manipulation de fichiers n’est normalement pas 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 drapeau -i indique de faire un remplacement sur place.

Voir man perlrun pour plus de détails, y compris comment faire une sauvegarde du fichier original.

J’ai été surpris parce que je suis tombé sur cette …

Il y a une commande “replace” qui est livrée avec le paquet “mysql-server” , donc si vous l’avez installée essayez-la:

 # replace ssortingng 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 l’ homme pour en savoir plus sur ça …

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

Dans ce cas, il y a plusieurs façons de le faire.

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

 sed 's/abc/XYZ/g' outfile 

Si vous souhaitez éditer le fichier en place (comme si vous ouvriez le fichier dans un éditeur, le modifiez, puis l’enregistrez), fournissez les 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.

J’ai trouvé ce fil parmi d’autres et je suis d’accord pour dire qu’il contient les réponses les plus complètes, alors j’ajoute les miennes aussi:

1) sed et ed sont tellement utiles … à la main !!! Regardez ce code de @Johnny:

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

2) lorsque ma ressortingction est de l’utiliser par un script shell, aucune variable ne peut être utilisée à la place de abc ou de XYZ! Cela semble être en 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 l’a dit @Johnny, utilisez un ‘while read …’ mais, malheureusement, ce n’est pas la fin de l’histoire. Ce qui suit a 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//''/$server}" line="${line//''/$alias}" line="${line//''/$user}" line="${line//''/$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 nullglob est défini alors, il se comporte étrangement quand il y a une chaîne contenant un * comme dans

  ServerName www.example.com 

qui devient

  

il n'y a pas de cornière et Apache2 ne peut même pas charger!

4) Ce type d'parsing devrait être plus lent que la recherche et le remplacement en un clic mais, comme vous l'avez déjà vu, il existe 4 variables pour 4 modèles de recherche différents fonctionnant sur un seul cycle d'parsing!

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

Ceci est un ancien message, mais pour ceux qui veulent utiliser des variables comme @centurian, les guillemets simples signifient que rien ne sera étendu.

Un moyen simple d’obtenir des variables consiste à faire de la concaténation de chaînes car cela se fait par juxtaposition dans bash, les éléments suivants devraient fonctionner:

sed -i -e 's/'"$var1"'/'"$var2"'/g' /tmp/file.txt

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

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

Voir plus sur le site bash-hackers

Vous pouvez utiliser sed

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

Utilisez i pour ignorer le cas si vous ne savez pas si le texte à trouver est abc ou ABC ou AbC, …

Vous pouvez utiliser find et sed si vous n’avez pas maintenant votre nom de fichier:

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

Recherchez et remplacez tous les fichiers Python:

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

Faites attention si vous remplacez les URL par un caractère “/”.

Un exemple de la façon de le faire:

 sed -i "s%http://domain.com%http://www.domain.com/folder/%g" "test.txt" 

Extrait de: http://www.sysadmit.com/2015/07/linux-reemplazar-texto-en-archivos-con-sed.html

Si le fichier sur lequel vous travaillez n’est pas trop gros et que le stocker temporairement dans une variable ne pose pas de problème, vous pouvez utiliser la substitution de chaîne Bash sur tout le fichier en une seule fois:

 file_contents=$( /tmp/file.txt 

Le contenu entier 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 est que vous n’avez pas besoin de vous soucier que la chaîne de recherche ou de remplacement contienne le caractère de délimiteur sed (généralement, mais pas nécessairement /). Un inconvénient est de ne pas pouvoir utiliser des expressions régulières ou des opérations plus sophistiquées de sed.

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 d’utilisation de la ligne de commande:

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

Ceci est équivalent à @slim answer de ex editor qui est fondamentalement la même chose.

Voici quelques exemples pratiques.

Remplacement du texte avec bar dans le fichier:

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

Suppression des espaces blancs de fin de fichier pour plusieurs fichiers:

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

Voir également:

  • Comment éditer des fichiers de manière non interactive (par exemple dans un pipeline)? chez Vi SE

find ./ -type f -name “fichier * .txt” | xargs sed -i -e ‘s / abc / xyz / g’

Vous pouvez utiliser la commande rpl. Par exemple, vous souhaitez modifier le nom de domaine dans un projet PHP complet.

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

Ce n’est pas une raison évidente, mais c’est très rapide et utile. 🙂

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() 

Maintenant que ce thread semble avoir remplacé les chaînes (d’octets) par un autre langage que bash, voici une implémentation C stupide:

  /** * Usage: * ./replace "foobar" "foobaz" < input_file > output_file * Note: input_file and output_file should be different */ #include  #include  #include  typedef struct ssortingng_t { const char * value; size_t length; } ssortingng; struct parser_t { ssortingng match_text, replace_text; char * match_buffer; unsigned int match_buffer_index; enum { STATE_INVALID, STATE_IN, STATE_OUT } state; }; void parser_init(struct parser_t * parser, const char * match_text, const char * replace_text) { memset(parser, 0, sizeof(struct parser_t)); parser->match_text.value = match_text; parser->match_text.length = strlen(match_text); parser->replace_text.value = replace_text; parser->replace_text.length = strlen(replace_text); parser->state = STATE_OUT; parser->match_buffer = malloc(parser->match_text.length); } void parser_free(struct parser_t * parser) { free(parser->match_buffer); } void output_char(char current_char) { fwrite(&current_char, sizeof(char), 1, stdout); } void buffer_match(struct parser_t * parser, char current_char) { parser->match_buffer[parser->match_buffer_index++] = current_char; } void buffer_clear(struct parser_t * parser) { parser->match_buffer_index = 0; } void buffer_flush(struct parser_t * parser) { if (parser->match_buffer_index > 0) { fwrite(parser->match_buffer, sizeof(char), parser->match_buffer_index, stdout); buffer_clear(parser); } } int process_state_in(struct parser_t * parser, char current_char) { if (parser->match_text.value[parser->match_buffer_index] == current_char) { buffer_match(parser, current_char); return STATE_IN; } if (parser->match_buffer_index == parser->match_text.length) { fwrite(parser->replace_text.value, sizeof(char), parser->replace_text.length, stdout); buffer_clear(parser); output_char(current_char); return STATE_OUT; } if (parser->match_text.value[parser->match_buffer_index] != current_char) { buffer_flush(parser); output_char(current_char); return STATE_OUT; } return STATE_INVALID; } int process_state_out(struct parser_t * parser, char current_char) { if (parser->match_text.value[parser->match_buffer_index] == current_char) { buffer_match(parser, current_char); return STATE_IN; } if (parser->match_text.value[parser->match_buffer_index] != current_char) { buffer_flush(parser); output_char(current_char); return STATE_OUT; } return STATE_INVALID; } int main(int argc, char *argv[]) { char current_char; struct parser_t parser; if (argc != 3) { fprintf(stdout, "Usage:\n\t%s match_text replace_text < in_file > out_file\n\t# note in_file and out_file should be different.\n", argv[0]); return 0; } parser_init(&parser, argv[1], argv[2]); while (fread(&current_char, sizeof(char), 1, stdin) != 0) { switch (parser.state) { case STATE_IN: { parser.state = process_state_in(&parser, current_char); } break; case STATE_OUT: { parser.state = process_state_out(&parser, current_char); } break; default: fprintf(stderr, "Error: Invalid state.\n"); return -1; break; } } parser_free(&parser); return 0; } 

Comstackr et exécuter:

 $ cc replace.c -oreplace $ ./replace "foobar" "foobaz" < input_file > output_file