나는 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:
-depth
Jeff 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를 저장하고 루프가 끝날 때 복원하는 것이 좋습니다.