ProgramingTip

find 및 sed를 사용하여 재귀 적으로 파일 이름

bestdevel 2020. 10. 13. 08:13
반응형

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,하는 경우에 쉘로 당신이 실제로 필요하지 않습니다 findsed후 당신의 이름을 변경 배치를 달성하기 위해 ...

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. 프로세스whilefind통해 결과를 제공 하는 대체 루프를 사용하여 완벽하게 홀로 처리 할 수 ​​있습니다 .

따라서 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의 버전 sedfind핸들 제대로에 find printfsed -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존재하지 않는 위치에 파일을 보내는 것과 관련된 가능한 오류를 방지 하고 재귀 적 루핑에 대한 필요성을 최소화합니다. ( 사실 루프를 찾기가 힘들 수도 있습니다. )
  • 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-printfsed

그리고 이제 우리는 돌아 왔습니다

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

반응형