Як я можу зробити рекурсивний пошук та заміну з командного рядка?


84

Використовуючи оболонку типу bash або zshell, як я можу зробити рекурсивне "знайти та замінити"? Іншими словами, я хочу замінити кожне виникнення 'foo' на 'bar' у всіх файлах цього каталогу та його підкаталогах.


Альтернативний відповідь на ті ж питання можна знайти тут stackoverflow.com/questions/9704020 / ...
dunxd


Це може бути гарною ідеєю, щоб спробувати це in vim. Таким чином ви можете використовувати функцію підтвердження, щоб переконатися, що ви не поміняєте місцями, на що не збираєтесь. Я не впевнений, чи можна це зробити в каталозі широко.
Samy Bencherif

Відповіді:


104

Ця команда зробить це (тестується як на Mac OS X Lion, так і на Kubuntu Linux).

# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

Ось як це працює:

  1. find . -type f -name '*.txt'знаходить у поточному каталозі ( .) та нижче всі звичайні файли ( -type f), назви яких закінчуються.txt
  2. | передає висновок цієї команди (список імен файлів) наступній команді
  3. xargs збирає ці файли і передає їх по черзі sed
  4. sed -i '' -e 's/foo/bar/g'означає "відредагувати файл на місці без резервної копії та зробити наступну заміну ( s/foo/bar) кілька разів на рядок ( /g)" (див. man sed)

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


9
Ніколи не труба знаходити вихід на xargs без -print0опції. Ваша команда провалиться у файлах із пробілами тощо у своєму імені.
slhck

19
Крім того, просто find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +зроблю все це з GNU знаходкою.
Даніель Андерссон

6
Я отримую, sed: can't read : No such file or directoryколи запускаю find . -name '*.md' -print0 | xargs -0 sed -i '' -e 's/ä/ä/g', але find . -name '*.md' -print0дає список багатьох файлів.
Мартін Тома

8
Це працює для мене , якщо я видалити простір між -iі''
канадським Люком

3
Яке значення має ''після того, sed -iякою є ''роль?
Жас

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

Це знімає xargsзалежність.


4
Це не працює з GNU sed, тому вийде з ладу для більшості систем. GNU sedвимагає, щоб ви не ставили місця між -iі ''.
slhck

1
Прийняті відповіді краще справляють їх пояснення. але +1 для використання правильного синтаксису.
інструбхен

9

Якщо ви використовуєте Git, ви можете зробити це:

git grep -lz foo | xargs -0 sed -i '' -e 's/foo/bar/g'

-lперелічує лише імена файлів. -zдрукує нульовий байт після кожного результату.

Я закінчив це робити тому, що в деяких файлах проекту не було нової лінії в кінці файлу, а sed додав новий рядок навіть тоді, коли він не вніс інших змін. (Немає коментарів щодо того, чи має файли мати новий рядок наприкінці. 🙂)


1
Великий +1 для цього рішення. Решта find ... -print0 | xargs -0 sed ...рішень не тільки займають набагато більше часу, але й додають нові рядки до файлів, у яких вже не існує, що є неприємністю при роботі всередині git repo. git grepосвітлення швидко порівняно
Джош Купершмідт

3

Ось моя функція zsh / perl, яку я використовую для цього:

change () {
        from=$1 
        shift
        to=$1 
        shift
        for file in $*
        do
                perl -i.bak -p -e "s{$from}{$to}g;" $file
                echo "Changing $from to $to in $file"
        done
}

І я би виконав це за допомогою

$ change foo bar **/*.java

(наприклад)


2

Спробуйте:

sed -i 's/foo/bar/g' $(find . -type f)

Тестовано на Ubuntu 12.04.

Ця команда НЕ працює, якщо імена підкаталогів та / або імена файлів містять пробіли, але якщо у вас є, не використовуйте цю команду, оскільки вона не працюватиме.

Як правило, погана практика використовувати пробіли в іменах каталогів та назви файлів.

http://linuxcommand.org/lc3_lts0020.php

Подивіться "Важливі факти про назви файлів"


Спробуйте, коли у вас є файли (файли) з пробілами в їх назвах. (Існує головне правило, яке говорить: "Якщо щось здається занадто хорошим, щоб бути правдою, це, мабуть, так є." Якщо ви "виявили" рішення, яке є більш компактним, ніж все, що хто-небудь інший опублікував за 3½ роки, ви повинні запитати себе, чому це може бути.)
Скотт

1

Використовуйте цей скрипт оболонки

Зараз я використовую цей скрипт оболонки, який поєднує в собі речі, які я дізнався з інших відповідей та пошуку в Інтернеті. Я помістив його у файл, названий changeу папці на моєму, $PATHі зробив chmod +x change.

#!/bin/bash
function err_echo {
  >&2 echo "$1"
}

function usage {
  err_echo "usage:"
  err_echo '  change old new foo.txt'
  err_echo '  change old new foo.txt *.html'
  err_echo '  change old new **\*.txt'
  exit 1
}

[ $# -eq 0 ] && err_echo "No args given" && usage

old_val=$1
shift
new_val=$1
shift
files=$* # the rest of the arguments

[ -z "$old_val" ]  && err_echo "No old value given" && usage
[ -z "$new_val" ]  && err_echo "No new value given" && usage
[ -z "$files" ]    && err_echo "No filenames given" && usage

for file in $files; do
  sed -i '' -e "s/$old_val/$new_val/g" $file
done

0

Моє використання було те, що я хотів замінити foo:/Drive_Letterна foo:/bar/baz/xyz У моєму випадку я зміг це зробити із наступним кодом. Я був у тому самому місці каталогів, де було велика частина файлів.

find . -name "*.library" -print0 | xargs -0 sed -i '' -e 's/foo:\/Drive_Letter:/foo:\/bar\/baz\/xyz/g'

сподіваюся, що допомогло.


0

Наступна команда чудово працювала на Ubuntu та CentOS; проте в OS XI продовжували виникати помилки:

find . -name Root -exec sed -i 's/1.2.3.4\/home/foo.com\/mnt/' {} \;

sed: 1: "./Root": недійсний код команди.

Коли я намагався передавати парами через xargs, він спрацював нормально без помилок:

find . -name Root -print0 | xargs -0 sed -i '' -e 's/1.2.3.4\/home/foo.com\/mnt/'

Той факт , що ви змінили , -iщоб -i '', ймовірно , більш актуальним , ніж той факт , що ви змінили -execв -print0 | xargs -0. До речі, вам, мабуть, це не потрібно -e.
Скотт

0
# Recursively find and replace in files
find . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'

Вищезазначене працювало як шарм, але з пов’язаними каталогами я мушу додати -Lпрапор до нього. Кінцева версія виглядає так:

# Recursively find and replace in files
find -L . -type f -name "*.txt" -print0 | xargs -0 sed -i '' -e 's/foo/bar/g'
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.