find 및 sed를 사용하여 재귀 적으로 파일 이름
여러 디렉토리를 찾아보고 _test.rb로 끝나는 모든 파일의 이름을 _spec.rb로 끝내고 싶습니다. bash 로하는 방법을 전혀 알아 내지 못했던 것 데 이번에는 그것을 못 박은 약간의 노력을 기울일 생각했습니다. 나는 지금까지 짧게 말했지만 최선의 노력은 다음과 가능합니다.
find spec -name "*_test.rb" -exec echo mv {} `echo {} | sed s/test/spec/` \;
NB : exec 나중에 추가 에코가 있으므로 테스트하는 동안 명령이 실행되는 대신 인쇄됩니다.
줄 때 일치하는 각 파일 이름에 대한 출력은 다음과 같습니다.
mv original original
즉, sed에 의해 대체가 감소했습니다. 속임수는 무엇입니까?
이더 sed
리움 {}
을 입력으로 받기 때문에 발생합니다 .
find . -exec echo `echo "{}" | sed 's/./foo/g'` \;
foofoo
디렉토리의 각 파일에 대해 재귀 적으로 인쇄 합니다. 이 동작의 이유는 파이프 라인이 전체 명령을 확장 할 때 셸에 의해 한 번 실행되기입니다.
을 통해 명령 셸을 실행하지 않고 파이프 라인이나 역 따옴표에, 대한 개념이 없기 때문에 모든 파일에 대해 sed
파이프 라인을 find
실행 하는 방식으로 파이프 라인 을 인용 할 방법 find
이 없습니다. GNU findutils 매뉴얼은 파이프 라인을 별도의 셸 펼쳐서 계속 작업을 수행하는 방법을 설명합니다.
#!/bin/sh
echo "$1" | sed 's/_test.rb$/_spec.rb/'
(이 sh -c
모든 것을 하나의 명령으로 수행하기 위해 사용하는 비뚤어진 방법 과 시도 많은 것입니다.)
원래 문제에 가장 가까운 방법으로 해결 비용 xargs "args per command line"옵션을 사용하는 것이 좋습니다.
find . -name *_test.rb | sed -e "p;s/test/spec/" | xargs -n2 mv
현재 작업 디렉토리에서 파일을 재귀 적 으로 찾고 , 원래 파일 이름 ( p
)과 수정 된 이름 ( s/test/spec/
)을 반향하여 모두 mv
쌍 ( xargs -n2
)으로 공급합니다 . 이 경우 경로 자체에는 많은 것이 test
있습니다.
다음과 같은 다른 방법을 고려할 수 있습니다.
for file in $(find . -name "*_test.rb")
do
echo mv $file `echo $file | sed s/_test.rb$/_spec.rb/`
done
이게 더 짧아요
find . -name '*_test.rb' -exec bash -c 'echo mv $0 ${0/test.rb/spec.rb}' {} \;
원하는 경우 sed없이 수행 할 수 있습니다.
for i in `find -name '*_test.rb'` ; do mv $i ${i%%_test.rb}_spec.rb ; done
${var%%suffix}
스트립 suffix
의 값으로부터 var
.
또는 sed를 사용하여 수행 비용 :
for i in `find -name '*_test.rb'` ; do mv $i `echo $i | sed 's/test/spec/'` ; done
당신은 당신이 사용하고 있다고 언급 bash
,하는 경우에 쉘로 당신이 실제로 필요하지 않습니다 find
및 sed
후 당신의 이름을 변경 배치를 달성하기 위해 ...
bash
쉘로 사용 가정합니다 .
$ echo $SHELL
/bin/bash
$ _
... 그리고 소위 globstar
쉘 옵션을 활성화하고 가정합니다 .
$ shopt -p globstar
shopt -s globstar
$ _
... 그리고 마지막으로 rename
유틸리티 ( util-linux-ng
패키지에 있음)를 설치하고 가정합니다.
$ which rename
/usr/bin/rename
$ _
... 다음 다음과 같이 bash 한 줄로 일괄 이름을 바꿀 수 있습니다 .
$ rename _test _spec **/*_test.rb
( globstar
쉘 옵션은 떠들썩한 파티가 *_test.rb
디렉토리 계층 구조에 얼마나 많이 중첩되어 있더라도 일치 여부하는 모든 파일을 찾도록 보장합니다 help shopt
. 설정 방법을 옵션 찾는 데 사용 )
가장 쉬운 방법 :
find . -name "*_test.rb" | xargs rename s/_test/_spec/
가장 빠른 방법 (4 개가 검증 가정) :
find . -name "*_test.rb" | xargs -P 4 rename s/_test/_spec/
처리 할 파일이 많은 경우 xargs로 파이프 된 파일 이름 목록으로 인해 결과 명령 줄이되는 최대 길이를 초과 할 수 있습니다.
다음을 사용하여 시스템의 제한을 확인할 수 있습니다. getconf ARG_MAX
대부분의 리눅스 시스템에서 사용 free -b
하거나 사용 하는 cat /proc/meminfo
작업해야하는 RAM의 양을 사용할 수 있습니다 . 문맥 top
시스템 활동 모니터 앱을 사용하십시오.
더 안전한 방법 (작업 할 RAM이 1000000 바이트라고 가정) :
find . -name "*_test.rb" | xargs -s 1000000 rename s/_test/_spec/
다음은 파일 이름에 공백이있을 때 저에게 무효합니다. 아래 예제는 모든 .dar 파일의 이름을 .zip 파일로 재귀 적으로 변경합니다.
find . -name "*.dar" -exec bash -c 'mv "$0" "`echo \"$0\" | sed s/.dar/.zip/`"' {} \;
이를 위해 sed
. 프로세스while
를 find
통해 결과를 제공 하는 대체 루프를 사용하여 완벽하게 홀로 처리 할 수 있습니다 .
따라서 find
필요한 파일을 선택 하는 경우이 있는 경우 다음 구문을 사용하십시오.
while IFS= read -r file; do
echo "mv $file ${file%_test.rb}_spec.rb" # remove "echo" when OK!
done < <(find -name "*_test.rb")
이것은 find
파일을 파일링 _test.rb
하고 끝 에서 문자열 을 스트라이핑 하고 추가 하는 모든 파일의 이름을 바꿉니다 _spec.rb
.
이 단계 에서는 가장 짧은 일치 패턴 "문자열"을 제거하는 쉘 변수 확장 을 사용 ${var%string}
합니다 $var
.
$ file="HELLOa_test.rbBYE_test.rb"
$ echo "${file%_test.rb}" # remove _test.rb from the end
HELLOa_test.rbBYE
$ echo "${file%_test.rb}_spec.rb" # remove _test.rb and append _spec.rb
HELLOa_test.rbBYE_spec.rb
예를 참조하십시오.
$ tree
.
├── ab_testArb
├── a_test.rb
├── a_test.rb_test.rb
├── b_test.rb
├── c_test.hello
├── c_test.rb
└── mydir
└── d_test.rb
$ while IFS= read -r file; do echo "mv $file ${file/_test.rb/_spec.rb}"; done < <(find -name "*_test.rb")
mv ./b_test.rb ./b_spec.rb
mv ./mydir/d_test.rb ./mydir/d_spec.rb
mv ./a_test.rb ./a_spec.rb
mv ./c_test.rb ./c_spec.rb
Ruby (1.9 이상)가있는 경우
ruby -e 'Dir["**/*._test.rb"].each{|x|test(?f,x) and File.rename(x,x.gsub(/_test/,"_spec") ) }'
내가 좋아하는 ramtam의 대답에서 찾기 부분은 작동하지만 경로에 공백이 있으면 나머지는 작동하지 않습니다. 나는 sed에 너무 익숙하지 않지만 그 대답을 다음과 같이 있고 수 있습니다.
find . -name "*_test.rb" | perl -pe 's/^((.*_)test.rb)$/"\1" "\2spec.rb"/' | xargs -n2 mv
내 사용 사례에서 최종 명령이 더 발생 해 보이기 때문에 이와 같은 변경이 정말 필요했습니다
find . -name "olddir" | perl -pe 's/^((.*)olddir)$/"\1" "\2new directory"/' | xargs -n2 mv
나는 그것을 다시 할 마음이 없지만 Commandline Find Sed Exec 에 대한 대답으로 썼습니다 . 거기에서 질문자는 디렉토리 하나 또는 둘 을 제외하고 전체 트리를 이동하는 방법을 알고 싶었고 문자열 "OLD" 를 포함하는 모든 파일과 디렉토리의 이름을
"NEW" 대신 포함 하는 방법을 알고 싶었습니다 .아래에 얼마나 자세한 설명이필요한지 설명하는 것 외에도 점에서 고유 할 수있는 것이 있습니다. 요청 된 작업 수행을 수행하기 위해 수행해야 수행 생각하는 모든 명령을 수행하고 변수에 저장하는 것을 제외하고 기본적으로 수행하는 것을 수행하지 않습니다.
또한 가능한 한 루프 를 명시 적으로 방지 합니다. sed
하나 이상의 패턴 일치에 대한 재귀 검색 외에 내가 아는 한 다른 재귀는 없습니다.
그리고 마지막으로, 완전히 이것은 null
구분되어 null
있습니다 ... 나는 당신이 그것을 가져야한다고 생각하지 않습니다 .
그건 그렇고, 이것은 정말 빠 사용합니다. 보기 :
% _mvnfind() { mv -n "${1}" "${2}" && cd "${2}"
> read -r SED <<SED
> :;s|${3}\(.*/[^/]*${5}\)|${4}\1|;t;:;s|\(${5}.*\)${3}|\1${4}|;t;s|^[0-9]*[\t]\(mv.*\)${5}|\1|p
> SED
> find . -name "*${3}*" -printf "%d\tmv %P ${5} %P\000" |
> sort -zg | sed -nz ${SED} | read -r ${6}
> echo <<EOF
> Prepared commands saved in variable: ${6}
> To view do: printf ${6} | tr "\000" "\n"
> To run do: sh <<EORUN
> $(printf ${6} | tr "\000" "\n")
> EORUN
> EOF
> }
% rm -rf "${UNNECESSARY:=/any/dirs/you/dont/want/moved}"
% time ( _mvnfind ${SRC=./test_tree} ${TGT=./mv_tree} \
> ${OLD=google} ${NEW=replacement_word} ${sed_sep=SsEeDd} \
> ${sh_io:=sh_io} ; printf %b\\000 "${sh_io}" | tr "\000" "\n" \
> | wc - ; echo ${sh_io} | tr "\000" "\n" | tail -n 2 )
<actual process time used:>
0.06s user 0.03s system 106% cpu 0.090 total
<output from wc:>
Lines Words Bytes
115 362 20691 -
<output from tail:>
mv .config/replacement_word-chrome-beta/Default/.../googlestars \
.config/replacement_word-chrome-beta/Default/.../replacement_wordstars
참고 : 위의 function
가능성이 필요합니다 GNU
의 버전 sed
과 find
핸들 제대로에 find printf
와 sed -z -e
와 :;recursive regex test;t
통화. 기능을 복제 할 수 있습니다.
이것은 당신이 원하는 모든 것을 처음부터 끝까지 거의 소란스럽게 할 것입니다. 내가 그랬어 fork
와 함께 sed
, 그러나 나는 또한 몇 가지 연습을했다 sed
내가 여기 왜의 너무 재귀 분기 기술을. 이발소에서 할인 헤어컷을 선택할 수 있습니다. 워크 플로는 다음과 가변합니다.
rm -rf ${UNNECESSARY}
- 모든 종류의 데이터를 삭제하거나 파괴 할 수있는 기능을 호출하지 않았습니다.
./app
원치 할 수 있었 는지했습니다 . 미리 삭제하거나 다른 곳으로 이동하거나 또는 프로그래밍 방식으로 수행 하도록\( -path PATTERN -exec rm -rf \{\} \)
루틴을 빌드 할 수find
있습니다.
- 모든 종류의 데이터를 삭제하거나 파괴 할 수있는 기능을 호출하지 않았습니다.
_mvnfind "${@}"
- 인수를 선언하고 함수를 호출하십시오.
${sh_io}
함수의 반환을 저장한다는 점에서 특히 중요합니다.${sed_sep}
가까운 두 번째에 온다.sed
함수에서의 재귀 를 참조하는 데 사용되는 임의의 URL 입니다.${sed_sep}
작동하는 경로가 또는 파일 이름에서 잠재적으로 발견 될 수있는 값으로 설정되어 있으면 ... 글쎄, 그렇게하지 마십시오.
- 인수를 선언하고 함수를 호출하십시오.
mv -n $1 $2
- 전체 트리가 처음부터 이동됩니다. 그것은 많은 두통을 덜어 줄 것입니다. 나를 믿어. 나머지 작업은 파일 시스템 메타 데이터의 문제입니다. 예를 들어이 드라이브를 한에서 다른 드라이브로 이동하거나 어떤 종류의 파일 시스템 경계를 넘어 이동하는 경우 하나의 명령으로 한 번에 수행하는 것이 좋습니다. 또한 더 안전합니다. 에 대한
-noclobber
옵션 세트에 유의하십시오mv
. 이 함수는 이미 존재${SRC_DIR}
하는 곳에 배치하지 않습니다${TGT_DIR}
.
- 전체 트리가 처음부터 이동됩니다. 그것은 많은 두통을 덜어 줄 것입니다. 나를 믿어. 나머지 작업은 파일 시스템 메타 데이터의 문제입니다. 예를 들어이 드라이브를 한에서 다른 드라이브로 이동하거나 어떤 종류의 파일 시스템 경계를 넘어 이동하는 경우 하나의 명령으로 한 번에 수행하는 것이 좋습니다. 또한 더 안전합니다. 에 대한
read -R SED <<HEREDOC
- 여기에 sed의 모든 명령을 찾아서 번거 로움을 피하고 아래의 sed에 피드 할 변수로 읽어 들었습니다. 아래 설명.
find . -name ${OLD} -printf
- 우리는
find
과정을 시작합니다 . 와find
우리는 우리가 이미 장소 - 투 - 장소의 모든했기 때문에 이름을 바꾸는 필요가 무엇을 검색mv
함수의 첫 번째 명령과 함께 작업을. 오히려보다가 어떤 직접적인 조치를 취할find
처럼,exec
예를 들어, 우리는 대신에 동적으로 명령 줄을 구축하는 데 사용할, 전화-printf
.
- 우리는
%dir-depth :tab: 'mv '%path-to-${SRC}' '${sed_sep}'%path-again :null delimiter:'
find
필요한 파일을 찾은 후 이름 변경을 처리하는 데 필요한 대부분 의 명령을 직접 빌드하고 인쇄합니다 .%dir-depth
각 줄의 시작은 우리가 아직 이름을 바꿀 수있는 부모 객체 트리에서 파일이나 디렉토리의 이름을 변경하려고하지 않을 수 있도록하는 데 도움이됩니다에 압정으로 고정.find
모든 종류의 최적화 기술을 사용하여 파일 시스템 트리를 탐색하고 안전한 작업 순서로 필요한 데이터를 반환할지 확신 할 수 없습니다. 이것이 우리가 다음에 ...
sort -general-numerical -zero-delimited
- $ {SRC}와 가장 가까운 경로가 먼저 작동하도록
find
의 모든 출력을 정렬합니다%directory-depth
. 이렇게하면mv
존재하지 않는 위치에 파일을 보내는 것과 관련된 가능한 오류를 방지 하고 재귀 적 루핑에 대한 필요성을 최소화합니다. ( 사실 루프를 찾기가 힘들 수도 있습니다. )
- $ {SRC}와 가장 가까운 경로가 먼저 작동하도록
sed -ex :rcrs;srch|(save${sep}*til)${OLD}|\saved${SUBSTNEW}|;til ${OLD=0}
- 나는 이것이 전체 스크립트에서 유일한 루프라고 생각하며
%Path
교체가 필요한 $ {OLD} 값이 둘 이상 포함 된 경우 각 문자열에 대해 인쇄 된 두 번째 루프 만 반복 합니다. 내가 상상 한 다른 모든 솔루션에는 두 번째sed
프로세스가 포함되어 있으며 짧은 루프는 바람직하지 않을 수 있지만 확실히 전체 프로세스를 생성하고 분기하는 것보다 좋습니다. - 기본적으로
sed
여기에서하는 것은 $ {sed_sep}를 검색 한 다음 찾은 다음 $ {OLD}를 찾을 때까지 발견 한 모든 문자와이를 저장 한 다음 $ {NEW}로 바꿉니다. 그런 다음 $ {sed_sep}로 돌아가서 문자열에서 두 번 이상 발생하는 경우 $ {OLD}를 다시 찾습니다. 발견되지 않으면 수정 된 문자열을 인쇄하고stdout
(다음에 다시 포착) 루프를 종료합니다. - 이렇게하면 전체 문자열을 구문 분석
mv
할 필요가 없으며 물론 $ {OLD}를 포함해야하는 명령 문자열 의 첫 번째 절반이 이를 포함하고 두 번째 절반은 삭제하는 데 필요한 횟수만큼 변경됩니다.mv
의 대상 경로에있는 $ {OLD} 이름입니다 .
- 나는 이것이 전체 스크립트에서 유일한 루프라고 생각하며
sed -ex...-ex search|%dir_depth(save*)${sed_sep}|(only_saved)|out
- 여기서 두 번의
-exec
호출은 1 초도 걸리지 않습니다fork
. 첫 번째에서 우리는 $ {OLD}의 모든 참조를 $ {NEW}로 적절하게 변경하기 위해 필요에 따라 의 function 명령에서mv
제공하는대로 명령을 수정 했지만 그렇게하려면 일부를 사용해야했습니다. 최종 출력에 포함되지 않아야하는 임의의 참조 점. 따라서 필요한 모든 작업을 마치면 전달하기 전에 보류 버퍼에서 참조 포인트를 지우도록 지시합니다.find
-printf
sed
- 여기서 두 번의
그리고 이제 우리는 돌아 왔습니다
read
다음과 같은 명령이 수신됩니다.
% mv /path2/$SRC/$OLD_DIR/$OLD_FILE /same/path_w/$NEW_DIR/$NEW_FILE \000
윌 read
로 그것을 ${msg}
로서 ${sh_io}
기능의 의지 외부에 조사 할 수있다.
멋있는.
-마이크
onitake가 제안한 예제 를 따라 공백이있는 파일 이름을 처리 할 수있었습니다 .
이것은 하지 않는 경로에 공백이나 문자열을 포함하는 경우 휴식 test
:
find . -name "*_test.rb" -print0 | while read -d $'\0' file
do
echo mv "$file" "$(echo $file | sed s/test/spec/)"
done
이것은 모든 경우에 작동해야하는 예입니다. 재귀 적으로 작동하고 셸만 필요하며 공백이있는 파일 이름을 지원합니다.
find spec -name "*_test.rb" -print0 | while read -d $'\0' file; do mv "$file" "`echo $file | sed s/test/spec/`"; done
$ find spec -name "*_test.rb"
spec/dir2/a_test.rb
spec/dir1/a_test.rb
$ find spec -name "*_test.rb" | xargs -n 1 /usr/bin/perl -e '($new=$ARGV[0]) =~ s/test/spec/; system(qq(mv),qq(-v), $ARGV[0], $new);'
`spec/dir2/a_test.rb' -> `spec/dir2/a_spec.rb'
`spec/dir1/a_test.rb' -> `spec/dir1/a_spec.rb'
$ find spec -name "*_spec.rb"
spec/dir2/b_spec.rb
spec/dir2/a_spec.rb
spec/dir1/a_spec.rb
spec/dir1/c_spec.rb
귀하의 질문은 sed에 관한 것 같지만 재귀 이름 바꾸기의 목표를 달성하기 위해 여기에 준 다른 대답에서 뻔뻔스럽게 다음을 제안합니다 .bash의 재귀 이름 바꾸기
#!/bin/bash
IFS=$'\n'
function RecurseDirs
{
for f in "$@"
do
newf=echo "${f}" | sed -e 's/^(.*_)test.rb$/\1spec.rb/g'
echo "${f}" "${newf}"
mv "${f}" "${newf}"
f="${newf}"
if [[ -d "${f}" ]]; then
cd "${f}"
RecurseDirs $(ls -1 ".")
fi
done
cd ..
}
RecurseDirs .
find utils 및 sed 정규식 유형으로 이름 바꾸기를 수행하는보다 안전한 방법 :
mkdir ~/practice
cd ~/practice
touch classic.txt.txt
touch folk.txt.txt
다음과 같이 ".txt.txt"확장자를 제거하십시오.
cd ~/practice
find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} \;
대신에 +를 사용하면; 배치 모드에서 작업하기 위해 위의 명령은 일치하는 첫 번째 파일의 이름 만 바꾸고 '찾기'로 일치하는 파일의 전체 목록을 바꾸지 않습니다.
find . -name "*txt" -execdir sh -c 'mv "$0" `echo "$0" | sed -r 's/\.[[:alnum:]]+\.[[:alnum:]]+$//'`' {} +
트릭을 수행하는 멋진 oneliner가 있습니다. 특히 -n 2를 사용하여 xargs에 의해 여러 변수가 전달되는 경우 Sed는이 권한을 처리 할 수 없습니다. bash 대체는 다음과 같이 쉽게 처리 할 수 있습니다.
find ./spec -type f -name "*_test.rb" -print0 | xargs -0 -I {} sh -c 'export file={}; mv $file ${file/_test.rb/_spec.rb}'
-type -f를 추가하면 이동 작업이 파일로만 제한되고 -print 0은 경로의 빈 공간을 처리합니다.
질문과 관련이 있으므로이 게시물을 공유합니다. 자세한 정보를 제공하지 않아서 죄송합니다. 다른 사람에게 도움이되기를 바랍니다. http://www.peteryu.ca/tutorials/shellscripting/batch_rename
참고 URL : https://stackoverflow.com/questions/4793892/recursively-rename-files-using-find-and-sed
'ProgramingTip' 카테고리의 다른 글
카피 스트라 노를 사용하여 이전에 배포 된 버전으로 어떻게 롤백하고 있습니까? (0) | 2020.10.13 |
---|---|
거기에서 어디로의 빠른 교차 (0) | 2020.10.13 |
NuGet 업그레이드 문제 (0) | 2020.10.13 |
Android에서 페인트 플래그 제거 (0) | 2020.10.13 |
브라우저의 디버거 자체에서 동적으로로드 JavaScript (jQuery 사용)를 사용하는 방법은 무엇입니까? (0) | 2020.10.13 |