Найти и заменить внутри текстового файла из команды Bash

каков самый простой способ найти и заменить заданную входную строку, скажем abc и замените другой строкой, скажем XYZ в файле /tmp/file.txt?

Я пишу приложение и использую IronPython для выполнения команд через SSH - но я не знаю Unix так хорошо и не знаю, что искать.

Я слышал, что Bash, помимо того, что интерфейс командной строки, может быть очень мощным языком сценариев. Итак, если это правда, я предполагаю, что вы можете выполнить такие действия.

могу ли я сделать это с bash, и какой самый простой (одна строка) сценарий для достижения моей цели?

15 ответов


самый простой способ-использовать sed (или perl):

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

который вызовет sed для редактирования на месте из-за . Это можно назвать от bash.

Если вы действительно хотите использовать просто bash, то может работать следующее:

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

это циклы над каждой строкой, выполнение подстановки и запись во временный файл (не хочу забивать вход). Движение в конце просто временно переходит в исходное имя.


манипуляция файлами обычно не выполняется Bash, а программами, вызываемыми Bash, например:

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

на -i флаг говорит ему сделать замену на месте.

посмотреть man perlrun для получения более подробной информации, в том числе, как сделать резервную копию исходного файла.


Я был удивлен, потому что я наткнулся на это...

есть "вместо" команда, которая поставляется с пакетом "mysql-server", поэтому, если вы установили его, попробуйте:

# 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"

посмотреть человек вместо подробнее об этом...


Bash, как и другие оболочки, является просто инструментом для координации других команд. Обычно вы пытаетесь использовать стандартные команды UNIX, но, конечно, вы можете использовать Bash для вызова чего угодно, включая ваши собственные скомпилированные программы, другие скрипты оболочки, скрипты Python и Perl и т. д.

в этом случае есть несколько способов сделать это.

Если вы хотите прочитать файл и записать его в другой файл, выполняя поиск / замену, используйте sed:

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

Если вы хотите отредактировать файл на месте (как будто открываете файл в Редакторе, редактируете его, затем сохраняете) предоставьте инструкции редактору строк "ex"

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

Ex похож на vi без полноэкранного режима. Вы можете дать ему те же команды, что и в приглашении vi':'.


нашел эту тему среди других, и я согласен, что она содержит наиболее полные ответы, поэтому я добавляю свой тоже:

1) sed и ed так useful...by рука!!! Посмотрите на этот код от @Johnny:

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

2) когда мое ограничение состоит в том, чтобы использовать его скриптом оболочки, никакая переменная не может использоваться внутри вместо abc или XYZ! этой кажется, согласны с тем, что я понимаю по крайней мере. Итак, я не могу использовать:

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

но, что мы можем сделать? Как, @Johnny сказал использовать а пока почитайте...- но, к сожалению, это еще не конец истории. Со мной хорошо сработало следующее:

#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) Как видно, если nullglob установлен, он ведет себя странно, когда есть строка, содержащая * как в

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

что будет

<VirtualHost ServerName www.example.com

нет концевого углового кронштейна, и Apache2 даже не может загрузить!

4) Этот вид разбора должен быть медленнее, чем один поиск и замена, но, как вы уже видели, там есть 4 переменные для 4 разных моделей поиск одного только цикла разбора!

наиболее подходящее решение, которое я могу придумать с заданными предположениями проблемы.


Это старый пост, но для тех, кто хочет использовать переменные, как @centurian сказал, что одинарные кавычки ничего не будут расширены.

простой способ получить переменные - это сделать конкатенацию строк, так как это делается путем сопоставления в bash, должно работать следующее:

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


вы можете использовать sed

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

используйте i для случая игнорирования, если вы не уверены, что текст найти abc или ABC или AbC,...

вы можете использовать find и sed, если теперь у вас нет имени файла:

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

найти и заменить во всех файлах python:

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

вы также можете использовать команду ed для поиска в файле и замены:

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

подробнее о bash-хакеры сайта


будьте осторожны, если вы заменяете URL-адреса символом"/".

пример того, как это сделать:

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

извлечено из: http://www.sysadmit.com/2015/07/linux-reemplazar-texto-en-archivos-con-sed.html


Если файл, над которым вы работаете, не такой большой, и временное хранение его в переменной не является проблемой, то вы можете использовать замену строки Bash на весь файл сразу - нет необходимости переходить по строкам:

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

все содержимое файла будет рассматриваться как одна длинная строка, включая переносы строк.

XYZ может быть переменной, например,$replacement, и одним из преимуществ не использовать sed здесь является то, что вам не нужно беспокоиться о том, что строка поиска или замены может содержать символ разделителя шаблона sed (обычно, но не обязательно, /). Недостатком является невозможность использования регулярных выражений или каких-либо более сложных операций sed.


чтобы редактировать текст в файле неинтерактивно, вам нужен текстовый редактор на месте, такой как vim.

вот простой пример, как использовать его из командной строки:

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

это эквивалентно @slim ответ of ex редактор, который является в основном то же самое.

вот несколько ex практические примеры.

Замена текста foo С bar в файле:

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

удаление трейлинг пробелы для нескольких файлов:

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

Читайте также:


найти ./ - введите f-name " file*.txt " /xargs sed-i-e 's/abc/xyz / g'


вы можете использовать команду РПЛ. Например, вы хотите изменить доменное имя во всем проекте php.

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

Это не ясно bash причины, но это очень быстро и полезно. :)


вы также можете использовать python в скрипте bash. У меня не было большого успеха с некоторыми из лучших ответов здесь, и я обнаружил, что это работает без необходимости циклов:

#!/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()

теперь, когда этот поток, похоже, превратился в замену строк (байтов) на любой другой язык, кроме bash, вот глупая реализация C:

<!-- 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;
}

скомпилировать и запустить:

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