Зміна декількох файлів


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

18
Якщо файлів так багато, ви порушите обмеження в командному рядку for. Щоб убезпечити себе від цього, вам доведеться скористатисяfind ... | xargs ...
glenn jackman

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

4
див. оновлену відповідь. якщо вам потрібна додаткова інформація, будь ласка, задайте офіційний запитання, щоб люди могли вам допомогти.
lenik

5
У команді sed потрібно використовувати "$i"замість того, $iщоб уникнути поділу слів на назви файлів з пробілами. Інакше це дуже приємно.
Wildcard

4
Щодо списку, я вважаю, що різниця полягає в тому, що forце частина синтаксису мови, а не просто вбудований. Бо sed -i 's/old/new' *розширення *must ALL має передаватися як аргумент до sed, і я впевнений, що це має відбутися до того, як sedпроцес можна навіть запустити. Використовуючи forцикл, повний аргумент (розширення *) ніколи не передається як команда, лише зберігається в пам'яті оболонки і повторюється через. Я взагалі не маю на це посилання, хоча, мабуть, саме в цьому різниця. (Я хотів би почути від когось більш обізнаного ...)
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 . -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 ./і пари одиничних лапок після -iдля OSX.
шелебід

Команда find працює, оскільки вона надається ealfonso, ./дорівнює .і -iмає лише параметр резервного копіювання.
ухаусбранд

-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щоб уникнути папок gitgrep -rl <old> . | grep -v \.git | xargs sed -i 's/<old>/<new>/g'
Martin Lyne

найкраще рішення для 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_includes \ / supercache_config / g; s / basic_proxy_config / proxy_include \ / basic_proxy_config / g" доступні сайти / * Не забудьте видалити * .bak файли, коли ви закінчите файл. система гігієни ради.
Йосія

19

@PaulR розмістив це як коментар, але люди повинні сприймати це як відповідь (і ця відповідь найкраще підходить для моїх потреб):

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

Це працюватиме для помірної кількості файлів, ймовірно, на порядку десятків, але, ймовірно, не на мільйони .


Припустимо, у вас зафіксовані косої риски. Ще один приклад із файловими шляхами 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
вихід цієї команди пошуку розширюється, тому це не вирішує проблему. Натомість слід скористатись -exec
ealfonso

@erjoalgo це працює, оскільки команда sed може обробляти кілька вхідних файлів. Розширення команди знайти саме те, що було потрібно для її роботи.
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

ти можеш зробити

' xxxx ' текст u пошуку і замінить його на ' 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і ґрунтувалося на відповіді Вася Новикова на петлі над кортежами в баш .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.