Как я могу заменить строку в файле (файлах)?

Я обычно использую di:

$ di
Filesystem         Mount               Size     Used    Avail %Used fs Type
/dev/sda1          /                  22.3G    13.1G     9.2G  59%  jfs    
udev               /dev              996.4M   200.0K   996.2M   0%  tmpfs  
tmpfs              /dev/shm         1001.6M       0   1001.6M   0%  tmpfs  
/dev/sda2          /home              50.2G    32.2G    17.9G  64%  jfs 
776
21.01.2017, 02:25
7 ответов

1. Замена всех случаев одной строки с другим во всех файлах в текущем каталоге:

Это для случаев, где Вы знаете, что каталог содержит только регулярные файлы и что Вы хотите обработать все нескрытые файлы. Если это не так используйте подходы в 2.

Все sed решения в этом ответе принимают GNU sed. При использовании FreeBSD или OS/X, замены -i с -i ''. Также обратите внимание что использование -i переключатель с любой версией sed имеет определенные последствия безопасности файловой системы и нецелесообразен в любом сценарии, который Вы планируете распределить всегда.

  • Не рекурсивный, файлы в этом каталоге только:

    sed -i -- 's/foo/bar/g' *
    perl -i -pe 's/foo/bar/g' ./* 
    

    ( perl каждый перестанет работать для имен файлов, заканчивающихся в | или пространство)).

  • Рекурсивные, регулярные файлы (включая скрытые) в этом и всех подкаталогах

    find . -type f -exec sed -i 's/foo/bar/g' {} +
    

    Если Вы используете zsh:

    sed -i -- 's/foo/bar/g' **/*(D.)
    

    (может перестать работать, если список является слишком большим, посмотрите zargs работать вокруг).

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

    ( shopt -s globstar dotglob;
        for file in **; do
            if [[ -f $file ]] && [[ -w $file ]]; then
                sed -i -- 's/foo/bar/g' "$file"
            fi
        done
    )
    

    Файлы выбраны, когда они - фактические файлы (-f), и они перезаписываемы (-w).

2. Замена, только если имя файла соответствует другой строке / имеет определенное расширение / имеет определенный тип и т.д.:

  • Нерекурсивный, файлы в этом каталоге только:

    sed -i -- 's/foo/bar/g' *baz*    ## all files whose name contains baz
    sed -i -- 's/foo/bar/g' *.baz    ## files ending in .baz
    
  • Рекурсивные, регулярные файлы в этом и всех подкаталогах

    find . -type f -name "*baz*" -exec sed -i 's/foo/bar/g' {} +
    

    При использовании удара (фигурные скобки стараются не устанавливать опции глобально):

    ( shopt -s globstar dotglob
        sed -i -- 's/foo/bar/g' **baz*
        sed -i -- 's/foo/bar/g' **.baz
    )
    

    Если Вы используете zsh:

    sed -i -- 's/foo/bar/g' **/*baz*(D.)
    sed -i -- 's/foo/bar/g' **/*.baz(D.)
    

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

  • Если файл имеет определенный тип, например, исполняемый файл (см. man find для большего количества опций):

    find . -type f -executable -exec sed -i 's/foo/bar/g' {} +
    

    zsh:

    sed -i -- 's/foo/bar/g' **/*(D*)
    

3. Замена, только если строка найдена в определенном контексте

  • Замена foo с bar только если существует a baz позже та же строка:

    sed -i 's/foo\(.*baz\)/bar\1/' file
    

    В sed, использование \( \) сохраняет то, что находится в круглых скобках, и можно затем получить доступ к нему с \1. Существует много изменений этой темы, чтобы узнать больше о таких регулярных выражениях, видеть здесь.

  • Замена foo с bar только если foo найден на 3-м столбце (поле) входного файла (принимающий разделенные от пробела поля):

    gawk -i inplace '{gsub(/foo/,"baz",$3); print}' file
    

    (потребности gawk 4.1.0 или более новый).

  • Поскольку другое поле просто использует $N где N количество интересующей области. Для другого разделителя полей (: в этом примере) использование:

    gawk -i inplace -F':' '{gsub(/foo/,"baz",$3);print}' file
    

    Другое использование решения perl:

    perl -i -ane '$F[2]=~s/foo/baz/g; $" = " "; print "@F\n"' foo 
    

    Примечание: оба awk и perl решения будут влиять на интервал в файле (удалите начальные и конечные пробелы и преобразуйте последовательности пробелов к одному пробелу в тех строках то соответствие). Для другого поля использовать $F[N-1] где N полевое число, которое Вы хотите и для другого использования разделителя полей ( $"=":" устанавливает выходного разделителя полей на :):

    perl -i -F':' -ane '$F[2]=~s/foo/baz/g; $"=":";print "@F"' foo 
    
  • Замена foo с bar только на 4-й строке:

    sed -i '4s/foo/bar/g' file
    gawk -i inplace 'NR==4{gsub(/foo/,"baz")};1' file
    perl -i -pe 's/foo/bar/g if $.==4' file
    

4. Несколько операций замены: замена различными строками

  • Можно объединиться sed команды:

    sed -i 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
    

    Знайте тот порядок вопросы (sed 's/foo/bar/g; s/bar/baz/g' займет место foo с baz).

  • или команды Perl

    perl -i -pe 's/foo/bar/g; s/baz/zab/g; s/Alice/Joan/g' file
    
  • Если у Вас есть большое количество шаблонов, легче сохранить Ваши шаблоны и их замены в a sed файл сценария:

    #! /usr/bin/sed -f
    s/foo/bar/g
    s/baz/zab/g
    
  • Или, если у Вас есть слишком много пар шаблона для вышеупомянутого, чтобы быть выполнимыми, можно читать, пары шаблона из файла (два располагают с интервалами разделенные шаблоны, $pattern и $replacement, на строку):

    while read -r pattern replacement; do   
        sed -i "s/$pattern/$replacement/" file
    done < patterns.txt
    
  • Это будет довольно медленно для длинных списков шаблонов и больших файлов данных, таким образом, Вы могли бы хотеть считать шаблоны и создать a sed сценарий от них вместо этого. Следующее принимает <пространство>, разделитель разделяет список СООТВЕТСТВИЯ <пространство> пары ЗАМЕНЫ, происходящие one-per-line в файле patterns.txt :

    sed 's| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|' <patterns.txt |
    sed -f- ./editfile >outfile
    

    Вышеупомянутый формат в основном произволен и, например, не допускает <пространство> или в СООТВЕТСТВИЯ или в ЗАМЕНЫ. Метод является очень общим хотя: в основном, если можно создать поток вывода, который похож на a sed сценарий, затем можно получить тот поток как a sed сценарий путем определения sedфайл сценария как -stdin.

  • Можно объединить и связать несколько сценариев точно так же:

    SOME_PIPELINE |
    sed -e'#some expression script'  \
        -f./script_file -f-          \
        -e'#more inline expressions' \
    ./actual_edit_file >./outfile
    

    POSIX sed свяжет все сценарии в один в порядке, они появляются на командной строке. Ни одному из них не нужен конец в a \newline.

  • grep может работать тот же путь:

    sed -e'#generate a pattern list' <in |
    grep -f- ./grepped_file
    
  • При работе с фиксированными строками как шаблоны это - хорошая практика для выхода из метасимволов регулярного выражения. Можно сделать это скорее легко:

    sed 's/[]$&^*\./[]/\\&/g
         s| *\([^ ]*\) *\([^ ]*\).*|s/\1/\2/g|
    ' <patterns.txt |
    sed -f- ./editfile >outfile
    

5. Несколько операций замены: замените несколько шаблонов той же строкой

  • Замените любой из foo, bar или baz с foobar

    sed -Ei 's/foo|bar|baz/foobar/g' file
    
  • или

    perl -i -pe 's/foo|bar|baz/foobar/g' file
    
1035
27.01.2020, 19:27
  • 1
    @StéphaneChazelas благодарит за редактирование, он действительно чинил несколько вещей. Однако не удаляйте информацию, которая относится к удару. Не все используют zsh. Любой ценой добавьте zsh информация, но нет никакой причины удалить материал удара. Кроме того, я знаю, что использование оболочки для обработки текста не идеально, но существуют случаи, где это необходимо. Я отредактировал в лучшей версии моего исходного сценария, который создаст a sed сценарий вместо того, чтобы на самом деле использовать цикл оболочки для парсинга. Это может быть полезно, если у Вас есть несколько сотен пар шаблонов, например. –  terdon♦ 16.01.2015, 17:10
  • 2
    @terdon, Ваш удар каждый является неправильным. удар прежде 4.3 будет следовать за символьными ссылками при убывании. Также удар не имеет никакого эквивалента для (.) спецификатор globbing так не может использоваться здесь. (Вы скучаете по некоторым - также). Для цикла является неправильным (отсутствующий-r) и означает делать несколько передач в файлах и не добавляет преимущества по sed сценарию. –  Stéphane Chazelas 16.01.2015, 17:16
  • 3
    @terdon, Что делает -- после sed -i и перед командой замены указывают? –  Geek 28.09.2015, 14:29
  • 4
    @Geek это - вещь POSIX. Это показывает конец опций и позволяет Вам передать аргументы, запускающиеся с -. Используя его гарантирует, что команды будут работать над файлами с именами как -foo. Без него, -f был бы проанализирован как опция. –  terdon♦ 28.09.2015, 14:42
  • 5
    Будьте очень тщательным выполнением некоторых рекурсивных команд в репозиториях мерзавца. Например, решения, предоставленные в разделе 1 из этого ответа, на самом деле изменят внутренние файлы мерзавца в a .git каталог, и на самом деле портит Ваш контроль. Лучше работать в рамках/на определенных каталогов по имени. –  Pistos 19.04.2016, 17:44

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

Вы можно также использовать find и sed, но я считаю, что эта маленькая строчка perl работает прекрасно.

 perl -pi -w -e 's / search / replace / g;' * .php 
 
  • -e означает выполнение следующей строки кода.
  • -i означает редактирование на месте
  • -w предупреждение записи
  • -p цикл по входному файлу, печать каждой строки после применения к нему сценария.

Наилучшие результаты достигаются при использовании perl и grep (чтобы убедиться, что файл имеет поисковое выражение)

perl -pi -w -e 's/search/replace/g;' $( grep -rl 'search' )
26
27.01.2020, 19:27

С точки зрения пользователя, хороший и простой инструмент Unix, который отлично справляется со своей задачей, - это qsubst . Например,

% qsubst foo bar *.c *.h

заменит foo на bar во всех моих файлах C. Приятной особенностью является то, что qsubst выполнит query-replace , то есть покажет мне каждое вхождение foo и спросит, хочу ли я заменить его или нет. [Вы можете безоговорочно заменить (без запроса) на параметр -go , и есть другие варианты, например, -w , если вы хотите заменить только foo , когда это целое слово.]

Как это получить: qsubst был изобретен дер Маусом (от МакГилла) и размещен в comp.unix.sources 11 (7) в Август 1987 г. Существуют обновленные версии. Например, версия NetBSD qsubst.c, v 1.8 2004/11/01 компилируется и отлично работает на моем Mac.

7
27.01.2020, 19:27

Я использовал это:

grep -r "old_string" -l | tr '\n' ' ' | xargs sed -i 's/old_string/new_string/g'
  1. Список всех файлов, содержащих old_string .

  2. Замените новую строку в результате пробелами (чтобы список файлов можно было передать в sed .

  3. Запустите sed для этих файлов, чтобы заменить старую строку новой.

Обновление: Приведенный выше результат не будет выполнен для имен файлов, содержащих пробелы. Вместо этого используйте:

grep --null -lr "old_string" | xargs --null sed -i 's / old_string / new_string / g '

15
27.01.2020, 19:27

Хорошим r e pl acement Linux инструментом является rpl , который изначально был написан для проекта Debian, поэтому он доступен с apt -get install rpl в любом дистрибутиве, производном от Debian, и может быть для других, но в противном случае вы можете загрузить файл tar.gz в SourgeForge .

Простейший пример использования:

 $ rpl old_string new_string test.txt

Обратите внимание, что если строка содержит пробелы, ее следует заключить в кавычки. По умолчанию rpl заботится о заглавных буквах , но не о полных словах , но вы можете изменить эти значения по умолчанию с помощью параметров -i (игнорировать case) и -w (целые слова). Вы также можете указать несколько файлов :

 $ rpl -i -w "old string" "new string" test.txt test2.txt

Или даже указать расширения ( -x ) для поиска или даже поиска рекурсивно ( -R ) в каталоге:

 $ rpl -x .html -x .txt -R old_string new_string test*

Вы также можете искать / заменять в интерактивном режиме с помощью -p (подсказка) параметр:

Вывод показать количество замененных файлов / строк и тип поиска (с учетом регистра, целые / частичные слова), но он может не отображаться с помощью -q ( тихий режим ) вариант или даже более подробный, перечисляя номера строк, которые содержат совпадения каждого файла и каталога с параметром -v ( подробный режим ).

Также следует помнить о параметрах -e (honor e scapes), которые разрешают регулярные выражения , поэтому вы можете искать также вкладки ( \ t ), новые строки ( \ n ) и т. д. Даже вы можете использовать -f до принудительно разрешить (конечно, только если у пользователя есть права на запись) и -d для сохранения времени модификации`).

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

77
27.01.2020, 19:27

Вы можете использовать Vim в режиме Ex:

заменить строку ALF на BRA во всех файлах в текущем каталоге?

for CHA in *
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

сделать то же самое рекурсивно для подкаталогов?

find -type f -exec ex -sc '%s/ALF/BRA/g' -cx {} ';'

заменить, только если имя файла совпадает с другой строкой ?

for CHA in *.txt
do
  ex -sc '%s/ALF/BRA/g' -cx "$CHA"
done

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

ex -sc 'g/DEL/s/ALF/BRA/g' -cx file

заменить, если строка находится на определенном номере строки?

ex -sc '2s/ALF/BRA/g' -cx file

заменить несколько строк одной и той же заменой

ex -sc '%s/\vALF|ECH/BRA/g' -cx file

заменить несколько строк разными заменами

ex -sc '%s/ALF/BRA/g|%s/FOX/GOL/g' -cx file
15
27.01.2020, 19:27

ripgrep(имя командыrg)— это инструмент grep, но он также поддерживает поиск и замену.

$ cat ip.txt
dark blue and light blue
light orange
blue sky
$ # by default, line number is displayed if output destination is stdout
$ # by default, only lines that matched the given pattern is displayed
$ # 'blue' is search pattern and -r 'red' is replacement string
$ rg 'blue' -r 'red' ip.txt
1:dark red and light red
3:red sky

$ # --passthru option is useful to print all lines, whether or not it matched
$ # -N will disable line number prefix
$ # this command is similar to: sed 's/blue/red/g' ip.txt
$ rg --passthru -N 'blue' -r 'red' ip.txt
dark red and light red
light orange
red sky

rgне поддерживает вариант размещения -, так что вам придется сделать это самостоятельно

$ # -N isn't needed here as output destination is a file
$ rg --passthru 'blue' -r 'red' ip.txt > tmp.txt && mv tmp.txt ip.txt
$ cat ip.txt
dark red and light red
light orange
red sky

См. Документацию по регулярным выражениям Rust для ознакомления с синтаксисом и функциями регулярных выражений. Переключатель -Pвключает вариант PCRE2 . rgпо умолчанию поддерживает Unicode.

$ # non-greedy quantifier is supported
$ echo 'food land bark sand band cue combat' | rg 'foo.*?ba' -r 'X'
Xrk sand band cue combat

$ # unicode support
$ echo 'fox:αλεπού,eagle:αετός' | rg '\p{L}+' -r '($0)'
(fox):(αλεπού),(eagle):(αετός)

$ # set operator example, remove all punctuation characters except. ! and ?
$ para='"Hi", there! How *are* you? All fine here.'
$ echo "$para" | rg '[[:punct:]--[.!?]]+' -r ''
Hi there! How are you? All fine here.

$ # use -P if you need even more advanced features
$ echo 'car bat cod map' | rg -P '(bat|map)(*SKIP)(*F)|\w+' -r '[$0]'
[car] bat [cod] map

Как и grep, опция -Fпозволит сопоставлять фиксированные строки, удобная опция, которую, как мне кажется, sedтоже следует реализовать.

$ printf '2.3/[4]*6\nfoo\n5.3-[4]*9\n' | rg --passthru -F '[4]*' -r '2'
2.3/26
foo
5.3-29

Еще одна удобная опция — -U, которая включает многострочное сопоставление

$ # (?s) flag will allow. to match newline characters as well
$ printf '42\nHi there\nHave a Nice Day' | rg --passthru -U '(?s)the.*ice' -r ''
42
Hi  Day

rgтакже может работать с файлами стиля dos -

$ # same as: sed -E 's/\w+(\r?)$/123\1/'
$ printf 'hi there\r\ngood day\r\n' | rg --passthru --crlf '\w+$' -r '123'
hi 123
good 123

Другое преимущество rgзаключается в том, что он, вероятно, будет быстрее, чемsed

$ # for small files, initial processing time of rg is a large component
$ time echo 'aba' | sed 's/a/b/g' > f1
real    0m0.002s
$ time echo 'aba' | rg --passthru 'a' -r 'b' > f2
real    0m0.007s

$ # for larger files, rg is likely to be faster
$ # 6.2M sample ASCII file
$ wget https://norvig.com/big.txt
$ time LC_ALL=C sed 's/\bcat\b/dog/g' big.txt > f1
real    0m0.060s
$ time rg --passthru '\bcat\b' -r 'dog' big.txt > f2
real    0m0.048s
$ diff -s f1 f2
Files f1 and f2 are identical

$ time LC_ALL=C sed -E 's/\b(\w+)(\s+\1)+\b/\1/g' big.txt > f1
real    0m0.725s
$ time rg --no-unicode --passthru -wP '(\w+)(\s+\1)+' -r '$1' big.txt > f2
real    0m0.093s
$ diff -s f1 f2
Files f1 and f2 are identical
6
27.01.2020, 19:27

Теги

Похожие вопросы