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

194

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

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

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

shantanuo
источник
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)
#
Lenik
источник
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' {} +
ealfonso
источник
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

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

funroll
источник
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*

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

palswim
источник
Предположим, в ваших заменах есть косые черты. Еще один пример с 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*')
dkinzer
источник
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.

Bluesboy
источник
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 .

Lejuanjowski
источник