명령 행에서 재귀 찾기 및 바꾸기를 어떻게 수행 할 수 있습니까?


84

bash 또는 zshell과 같은 셸을 사용하여 재귀적인 '찾기 및 바꾸기'를 수행하려면 어떻게해야합니까? 즉,이 디렉토리와 하위 디렉토리의 모든 파일에서 모든 'foo'발생을 'bar'로 바꾸고 싶습니다.


동일한 질문에 대한 다른 답변은 여기를 참조하십시오. stackoverflow.com/questions/9704020/…
dunxd


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
-print0옵션 없이 xargs에 출력을 파이프로 연결하지 마십시오 . 이름이 공백 등이있는 파일에서는 명령이 실패합니다.
slhck

20
또한 find -name '*.txt' -exec sed -i 's/foo/bar/g' {} +GNU find 로이 모든 것을 할 것입니다.
Daniel Andersson

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많은 파일 목록을 제공합니다.
Martin Thoma

9
내가 사이에 공간을 제거하면 이것은 나를 위해 작동 -i합니다''
캐나다 누가

3
역할 ''sed -i무엇인지에 대한 의미는 무엇입니까 ''?
Jas

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

이것은 xargs의존성을 제거합니다 .


4
이것은 GNU sed에서 작동하지 않으므로 대부분의 시스템에서 실패합니다. GNU는 sed사이에 공백을 넣지하도록 요구 -i하고 ''.
slhck

1
받아 들여진 대답은 더 잘 설명해줍니다. 그러나 올바른 구문을 사용하려면 +1입니다.
orodbhen

9

Git을 사용하는 경우 다음을 수행 할 수 있습니다.

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

-l파일 이름 만 나열합니다. -z각 결과 뒤에 널 바이트를 인쇄합니다.

프로젝트의 일부 파일에는 파일 끝에 줄 바꿈이 없기 때문에이 작업을 끝내고 다른 변경 사항이 없어도 줄 바꿈을 추가했습니다. (파일 끝에 줄 바꿈이 있어야하는지에 대한 의견은 없습니다. 🙂)


1
이 솔루션의 경우 큰 +1입니다. 나머지 find ... -print0 | xargs -0 sed ...솔루션은 시간이 오래 걸리고 파일이없는 파일에 줄 바꿈을 추가하므로 git repo 내부에서 작업 할 때 성가신 것입니다. git grep비교해 보면 빠른 조명입니다.
Josh Kupershmidt

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)

우분투 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. BTW, 당신은 아마 필요하지 않습니다 -e.
Scott

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'
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.