파일 및 디렉터리 이름의 문자열을 재귀적으로 바꾸는 방법은 무엇입니까?

파일 및 디렉터리 이름의 문자열을 재귀적으로 바꾸는 방법은 무엇입니까?

나는 etckeeper의 git 복제본 을 가지고 있으며 etckeeper이름 이 usrkeeper../foo-etckeeper-bar./foo-usrkeeper-bar

관련 파일을 찾는 것은 간단합니다.

% find . -path '*etckeeper*' -print

그러나 실제로 이름을 바꾸는 방법을 모르겠습니다. 나는 다음 xargs과 결합을 시도했습니다 mv.

% find . -path '*etckeeper*' -print0 | xargs -0 -n 1 -J % bash -c mv % '$(echo' % \| sed \"s/etckeeper/usrkeeper/\" \)

가독성을 위해 이스케이프되지 않은 후반부는 다음과 같이 표시됩니다 xargs -0 -n 1 -J % bash -c mv % $(echo % | sed "s/etckeeper/usrkeeper/" ). 이에 대한 아이디어는 교체를 위해 $()파일 이름을 파이프하는 것입니다 sed.

여기서 문제는 bash -c실행에 필요한 명령이 단일 문자열이라는 것입니다. 그런 다음 매개변수를 위치 매개변수로 해석하기 시작합니다. 나는 모든 내용을 인용할 수 있습니다:

% find . -path '*etckeeper*' -print0 | xargs -0 -n 1 -J % bash -c 'mv % $(echo % | sed "s/etckeeper/usrkeeper/g" )'

하지만 지금은 xargs교체되지 않습니다 %. 이 문제를 어떻게 해결할 수 있나요? (또한 참고로, etckeeper이름을 포함하는 파일이 name 을 포함하는 디렉토리에 존재하는 경우 etckeeper디렉토리가 파일보다 먼저 이동되기 때문에 위의 작업은 실패합니다.)

답변1

이름 바꾸기 명령을 사용할 수 있습니다(참조:편집 1).

솔루션 1

합리적인 수의 파일/디렉토리의 경우 bash 4 옵션을 설정하여글로벌 스타(재귀 이름에는 적용되지 않습니다.편집 3):

shopt -s globstar
rename -n 's/etckeeper/userkeeper/g' **

솔루션 2

파일/디렉터리 수가 많은 경우 이름 바꾸기 및 찾기를 두 단계로 사용하여 방금 이름을 바꾼 디렉터리에서 파일 이름 바꾸기가 실패하는 것을 방지합니다(참조).편집 2):

find . -type f -exec rename 's/etckeeper/userkeeper/g' {} \;
find . -type d -exec rename 's/etckeeper/userkeeper/g' {} \;

편집 1:

두 가지 이름 바꾸기 명령이 있습니다.. 이 답변은 Debian 기반 배포판에서 사용할 수 있는 Perl 기반 이름 바꾸기 명령을 사용합니다. 데비안 기반이 아닌 배포판에 설치하려면 다음이 필요합니다.cpan에서 설치 가능아니면 잡아요.

편집 2:

-depthJeff Schaller가 주석에서 find 옵션을 지적했듯이 Process each directory's contents before the directory itself옵션별로 "정렬된" 조회만으로 -depth충분합니다.

find . -depth -exec rename 's/etckeeper/userkeeper/g' {} \;

편집 3

etckeeper/etckeeper/etckeeper해결 방법 1은 외부 수준이 내부 수준보다 먼저 처리되고 내부 수준에 대한 포인터가 쓸모 없게 되기 때문에 재귀적 이름 바꾸기 대상(예: )에는 작동하지 않습니다 . ( 첫 번째 rename 이후에 etckeeper/etckeeper/etckeeper이름이 지정되므로 usrkeeper/etckeeper/etckeeper이름을 etckeeper/etckeeper/및 로 바꾸면 etckeeper/etckeeper/etckeeper실패합니다). 옵션은 find동일한 문제를 해결합니다 -depth.

편집 4

Cas가 의견에서 지적했듯이,{} \; 대신 {} +를 사용합니다. - Perl 스크립트를 포크하는 경우(예: 파일/디렉토리당 한 번) 비용이 많이 듭니다.

답변2

귀하의 원래 질문은 실제로 대답하기 쉽습니다. ( xargs적어도 OS X에서는) -I다음을 수행하는 옵션도 있습니다.아니요대체 문자열이 유일한 매개변수여야 합니다. 따라서 호출은 다음과 같습니다.

% find . -path '*etckeeper*' -print0 | xargs -0 -n 1 -I % bash -c 'echo mv % $(echo % | sed "s/etckeeper/usrkeeper/g" )'

매우 간단합니다. 이제 올바른 순서로 이름을 바꾸겠습니다. 디렉토리가 먼저 인쇄될 것이라는 것이 밝혀졌습니다 find(하강하기 전에 디렉토리를 처리해야 하기 때문입니다). 따라서 우리가 해야 할 일은 파일 이름의 순서를 바꾸는 것뿐입니다.

% find . -path '*etckeeper*' -print | tail -r | xargs -n 1 -I % bash -c 'echo mv % $(echo % | sed "s/etckeeper/usrkeeper/g" )'

find -print0 | xargs -0로 전환했습니다 find -print | xargs. 이유는 무엇입니까? tail -r반전은 널 문자가 아닌 개행 문자를 기반으로 하기 때문입니다 . 전환하지 않으면 tail -r파이프한 것과 동일한 내용이 인쇄됩니다! 따라서 이제 스크립트가 더 정확해졌지만 개행 문자가 포함된 파일 이름에서도 중단됩니다.

만약 가지고 있지 않다면 tail -r한번 시도해 보시기 바랍니다 tac. 바라보다:파일의 줄 순서를 바꾸는 방법은 무엇입니까?스택 오버플로에.

지금 문제는 그 sed명령이 너무 공격적이라는 점이다. 예를 들어 다음과 같은 트리가 있습니다.

.
├── etckeeper-foo
│   └── etckeeper-bar.md
└── some-other-directory
    └── etckeeper-baz.md

mv다음과 같은 전화를 받게 됩니다 .

mv ./some-other-directory/etckeeper-baz.md ./some-other-directory/usrkeeper-baz.md
mv ./etckeeper-foo/etckeeper-bar.md ./usrkeeper-foo/usrkeeper-bar.md
mv ./etckeeper-foo ./usrkeeper-foo

첫 번째는 훌륭했지만 두 번째는 분명 그렇지 않았습니다. 아직 끝나지 않았기 때문입니다 mv ./etckeeper-foo ./usrkeeper-foo!

저희는 교체만 합니다마지막경로명 구성요소. basename다음을 사용하여 이를 수행 할 수 있습니다 dirname.

% find . -name '*etckeeper*' -print | tail -r | xargs -n 1 -I % bash -c 'echo mv % $(dirname %)/$(basename % | sed "s/etckeeper/usrkeeper/g" )'

내가 변경한 또 다른 사항 은 find -name.find -path

그러면 올바른 mv호출이 생성됩니다.

mv ./some-other-directory/etckeeper-baz.md ./some-other-directory/usrkeeper-baz.md
mv ./etckeeper-foo/etckeeper-bar.md ./etckeeper-foo/usrkeeper-bar.md
mv ./etckeeper-foo ./usrkeeper-foo

마침내. 다시 한 번 기억하세요.실패하다난해한 파일 이름과 관련하여. 또한 파일 이름이 특히 길면 매개변수가 너무 길어져서 xargs작동하지 않습니다 . mv이 경우 (적어도 OS X에서는) to를 전달하여 -x즉시 xargs실패하도록 지시할 수 있습니다.

답변3

for또 다른 해결책은 찾기 결과를 반복하고 mv발견된 파일에서 bash 문자열 교체를 수행하는 작은 스크립트를 사용하는 것입니다 .

IFS=$'\n'
for files in $(find . -name "*etckeeper*"); 
do 
  mv "$files ${files/etckeeper/usrkeeper}"
done

스크립트에서 이를 사용하지 않는 경우 원본 IFS를 저장하고 루프가 끝날 때 복원하는 것이 좋습니다.

관련 정보