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


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.