Изменить несколько файлов


194

Следующая команда корректно изменяет содержимое 2 файлов.

sed -i 's/abc/xyz/g' xaa1 xab1 

Но мне нужно динамически изменить несколько таких файлов, а я не знаю их имен. Я хочу написать команду, которая будет читать все файлы из текущего каталога, начиная с, xa*и sedдолжна изменить содержимое файла.


63
Вы имеете в виду sed -i 's/abc/xyz/g' xa*?
— Пол Р

3
Ответов здесь не достаточно. См. Unix.stackexchange.com/questions/112023/…
— Исаак

Ответы:


136

Еще лучше:

for i in xa*; do
    sed -i 's/asd/dfg/g' $i
done

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

Вот что происходит, когда файлов слишком много:

# grep -c aaa *
-bash: /bin/grep: Argument list too long
# for i in *; do grep -c aaa $i; done
0
... (output skipped)
#

19
Если файлов так много, вы нарушите ограничение командной строки в forкоманде. Чтобы защитить себя от этого, вы должны будете использоватьfind ... | xargs ...
— Гленн Джекман

1
Я не знаю реализацию, но шаблон "xa *" должен быть расширен в какой-то момент. Разве оболочка выполняет расширение иначе, forчем для echoили grep?
— Гленн Джекман

4
см обновленный ответ. если вам нужна дополнительная информация, пожалуйста, задайте официальный вопрос, чтобы люди могли помочь вам.
— lenik

5
В команде sed вы должны использовать "$i"вместо того, $iчтобы избежать разбиения слов в именах файлов с пробелами. В противном случае это очень приятно.
— Подстановочный

4
Что касается списка, я считаю, что разница в том, что он forявляется частью синтаксиса языка, а не просто встроенным. Поскольку sed -i 's/old/new' *расширение *ALL должно быть передано как arglist в sed, и я уверен, что это должно произойти еще до того, как sedпроцесс может быть запущен. Используя forцикл, полный arglist (расширение *) никогда не передается как команда, только сохраняется в памяти оболочки и повторяется. У меня нет никаких ссылок на это, хотя, просто кажется вероятным, что это разница. (Я хотел бы услышать от кого-то более знающего ...)
— Wildcard

167

Я удивлен, что никто не упомянул аргумент -exec для поиска, который предназначен для этого типа сценария использования, хотя он запускает процесс для каждого соответствующего имени файла:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} \;

В качестве альтернативы можно использовать xargs, который вызовет меньше процессов:

find . -type f -name 'xa*' | xargs sed -i 's/asd/dsg/g'

Или проще использовать + вариант exec вместо ;find, чтобы позволить find предоставить более одного файла на вызов подпроцесса:

find . -type f -name 'xa*' -exec sed -i 's/asd/dsg/g' {} +

8
Мне пришлось изменить команду в этом ответе примерно так: find ./ -type f -name 'xa*' -exec sed -i '' 's/asd/dsg/g' {} \;это расположение команды find ./и пара одинарных кавычек после -iOSX.
— Шелбидз

Команда find работает так, как она предоставляется ealfonso, ./равна .и после -iимеет только параметр backupsuffix.
— Uhausbrand

-execВариант находки вместе с {} +достаточно , чтобы решить эту проблему , как указано, и должна быть достаточно для большинства требований. Но xargsэто лучший выбор в целом, потому что он также позволяет параллельную обработку с -pопцией. Если расширение глобуса достаточно велико, чтобы переполнить длину командной строки, вы, вероятно, также выиграете от ускорения по сравнению с последовательным запуском.
— Амит Найду

78

Вы можете использовать grep и sed вместе. Это позволяет рекурсивно искать в подкаталогах.

Linux: grep -r -l <old> * | xargs sed -i 's/<old>/<new>/g'
OS X: grep -r -l <old> * | xargs sed -i '' 's/<old>/<new>/g'

For grep:
    -r recursively searches subdirectories 
    -l prints file names that contain matches
For sed:
    -i extension (Note: An argument needs to be provided on OS X)

3
Бонусом этого метода для меня было то, что я мог проскользнуть внутрь, grep -vчтобы избежать мерзких папокgrep -rl <old> . | grep -v \.git | xargs sed -i 's/<old>/<new>/g'
— Мартин Лайн

лучшее решение для Mac!
— маркиз Блаунт

30

Эти команды не будут работать по умолчанию, sedкоторый поставляется с Mac OS X.

От man 1 sed:

-i extension
             Edit files in-place, saving backups with the specified
             extension.  If a zero-length extension is given, no backup 
             will be saved.  It is not recommended to give a zero-length
             extension when in-place editing files, as you risk corruption
             or partial content in situations where disk space is exhausted, etc.

Пытался

sed -i '.bak' 's/old/new/g' logfile*

и

for i in logfile*; do sed -i '.bak' 's/old/new/g' $i; done

Оба работают нормально.


2
@sumek Вот пример терминальной сессии на OS X, которая показывает sed, заменяющий все вхождения: GitHub Gist
— funroll

Я использовал это для замены двух разных строк во всех файлах конфигурации моего сайта на одну строчку ниже. sed -i.bak "s / supercache_proxy_config / proxy_include \ / supercache_config / g; s / basic_proxy_config / proxy_include \ / basic_proxy_config / g" sites-available / * Не забудьте удалить файлы * .bak, когда вы закончите для файла ради гигиены системы.
— Иосия

19

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

sed -i 's/abc/xyz/g' xa*

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


Предположим, в ваших заменах есть косые черты. Еще один пример с filepaths sed -i 's|auth-user-pass nordvpn.txt|auth-user-pass /etc/openvpn/nordvpn.txt|g' *.ovpn.
— Лео Леопольд Герц,

10

Еще один более универсальный способ заключается в использовании find:

sed -i 's/asd/dsg/g' $(find . -type f -name 'xa*')

1
вывод этой команды find расширяется, поэтому это не решает проблему. Вместо этого вы должны использовать -exec
— ealfonso

@erjoalgo это работает, потому что команда sed может обрабатывать несколько входных файлов. Расширение команды find точно необходимо, чтобы она работала.
— dkinzer

это работает до тех пор, пока количество файлов не выходит за пределы командной строки.
— ealfonso

Это ограничение зависит только от ресурсов памяти, доступных для машины, и оно точно такое же, как ограничение для exec.
— dkinzer

4
Это просто не соответствует действительности. В приведенной выше команде $ (find. ...) расширяется в одну команду, которая может быть очень длинной, если имеется много подходящих файлов. Если он слишком длинный (например, в моей системе ограничение составляет около 2097152 символов), вы можете получить сообщение об ошибке: «Список аргументов слишком длинный», и команда не будет выполнена. Пожалуйста, Google эту ошибку, чтобы получить некоторую справку об этом.
— Ealfonso

2

Я использую findдля аналогичной задачи. Это довольно просто: вы должны передать это в качестве аргумента для sedэтого:

sed -i 's/EXPRESSION/REPLACEMENT/g' `find -name "FILE.REGEX"`

Таким образом, вам не нужно писать сложные циклы, и просто увидеть, какие файлы вы собираетесь изменить, просто запустите findперед запуском sed.


1
Это точно так же, как ответ @ dkinzer .
— г-н Тао

0

ты можешь сделать

' хххх ' текст, который вы ищете, и заменит его на ' yyyy '

grep -Rn '**xxxx**' /path | awk -F: '{print $1}' | xargs sed -i 's/**xxxx**/**yyyy**/'

0

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

Используя словарь / hashMap (ассоциативный массив) и переменные для sedкоманды, мы можем перебрать массив, чтобы заменить несколько строк. Включение в шаблон подстановочного знака name_patternпозволит заменить на месте файлы с шаблоном (это может быть что-то вроде name_pattern='File*.txt') в определенном каталоге ( source_dir). Все изменения написаны logfileвdestin_dir

#!/bin/bash
source_dir=source_path
destin_dir=destin_path
logfile='sedOutput.txt'
name_pattern='File.txt'

echo "--Begin $(date)--" | tee -a $destin_dir/$logfile
echo "Source_DIR=$source_dir destin_DIR=$destin_dir "

declare -A pairs=( 
    ['WHAT1']='FOR1'
    ['OTHER_string_to replace']='string replaced'
)

for i in "${!pairs[@]}"; do
    j=${pairs[$i]}
    echo "[$i]=$j"
    replace_what=$i
    replace_for=$j
    echo " "
    echo "Replace: $replace_what for: $replace_for"
    find $source_dir -name $name_pattern | xargs sed -i "s/$replace_what/$replace_for/g" 
    find $source_dir -name $name_pattern | xargs -I{} grep -n "$replace_for" {} /dev/null | tee -a $destin_dir/$logfile
done

echo " "
echo "----End $(date)---" | tee -a $destin_dir/$logfile

Сначала объявляется массив пар, каждая пара является строкой замены, затем WHAT1будет заменена на FOR1и OTHER_string_to replaceбудет заменена на string replacedв файле File.txt. В цикле считывается массив, первый член пары извлекается как, replace_what=$iа второй как replace_for=$j. Команда findищет в каталоге имя файла (которое может содержать подстановочный знак), и sed -iкоманда заменяет те же файлы, которые были определены ранее. Наконец я добавилgrep перенаправление в файл журнала, чтобы записать изменения, внесенные в файл (ы).

Это сработало для меня GNU Bash 4.3 sed 4.2.2и основано на ответе VasyaNovikov для Loop over tuples in bash .

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.