bash - une - sed supprimer ligne




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

Bash, comme d'autres shells, est juste un outil pour coordonner d'autres commandes. En général, vous essayez d'utiliser les 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 y a plusieurs façons de le faire.

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

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

Si vous voulez éditer le fichier en place (comme si l'ouverture du fichier dans un éditeur, l'éditer, puis l'enregistrer) fournissaient 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 qu'à l'invite ':' de vi.

Quelle est la méthode la plus simple pour trouver et remplacer une chaîne d'entrée donnée, disons abc , et la remplacer par une autre chaîne, disons XYZ dans le 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?


Ceci est un ancien article, mais pour quiconque veut utiliser des variables comme @centurian a déclaré que les guillemets simples signifient que rien ne sera développé.

Un moyen simple d'obtenir des variables est de faire une concaténation de chaîne car cela est fait par juxtaposition dans bash ce qui suit devrait fonctionner:

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


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

Il y a une commande "replace" qui est fournie avec le paquet "mysql-server" , 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"

Voyez l' homme remplacer pour plus sur ceci ...


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

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

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

2) quand ma restriction est de l'utiliser par un script shell, aucune variable ne peut être utilisée à l'intérieur à la place de abc ou XYZ! This 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 d'utiliser un 'tandis que lire ...' 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//'<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 nullglob est défini alors, il se comporte bizarrement 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 d'angle de fin et Apache2 ne peut même pas charger!

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

La solution la plus appropriée je peux penser aux hypothèses données du problème.


Le plus simple est d'utiliser sed (ou perl):

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

Ce qui invoquera sed pour effectuer une modification sur place en raison de 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 boucle sur chaque ligne, en effectuant une substitution et en écrivant dans un fichier temporaire (ne pas vouloir écraser l'entrée). Le mouvement à la fin se déplace simplement vers le nom d'origine.


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

<!-- language: c -->

/**
 * Usage:
 *      ./replace "foobar" "foobaz" < input_file > output_file
 * Note: input_file and output_file should be different
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

typedef struct string_t {
    const char * value;
    size_t length;
} string;

struct parser_t {
    string 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;
}

Compiler et exécuter:

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

Si le fichier sur lequel vous travaillez n'est pas très volumineux et que vous le stockez temporairement dans une variable, vous pouvez utiliser la substitution de chaînes Bash sur l'ensemble du fichier en une seule fois - pas besoin de le parcourir ligne par ligne:

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

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


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

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

En savoir plus sur le site de bash-hackers


Vous pouvez utiliser la commande rpl. Par exemple, vous voulez changer le nom de domaine dans le projet php entier.

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

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


Vous pouvez utiliser sed

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

Utilisez i pour ignorer le cas si vous n'êtes pas sûr que le texte à trouver est abc ou ABC ou AbC, ...

Vous pouvez utiliser find et sed si vous ne possédez pas votre nom de fichier:

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

Trouvez et remplacez dans tous les fichiers python:

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




ironpython