찾기 프로세스 중에 파일 이름을 변경할 수 있습니까?

찾기 프로세스 중에 파일 이름을 변경할 수 있습니까?

find{}"이 파일"(ish)을 나타내는 데 사용됩니다 . 다음과 같이 에 다양한 파일을 입력할 수 있습니다 myprog.

find ./tests/ -name *.in -exec myprog -i {} \;

안에 이름 바꾸는 방법 없나요 {}? 내 경우에는 -i입력 파일과 출력을 정의 했고 -o출력에서 ​​파일 이름을 약간 수정하여 "a.in"이 "a.out"을 생성하기를 원했습니다. 이상적으로는 다음을 달성하고 싶습니다.

find ./tests/ -name *.in -exec myprog -i {} -o {}.out \;

또한 출력 디렉터리의 경로가 다를 수 있습니다. 이 경우 출력이 도착하지 않을 수도 있지만 /tests/도착할 수도 있습니다 /tests_20220615/.

예제가 포함된 여러 페이지를 살펴봤지만 find유사한 내용이 나타나지 않으므로 "아니오"가 아닐까요?

Bash 또는 zsh에서 루프를 사용하여 이를 수행할 수 있는 방법이 있다는 것을 알고 있지만 가능한 함정 목록은 훌륭하고("nullglob"?!) find수행할 수 있다면 이 멍청한 놈에게는 더 안전해 보입니다.

답변1

find파일 경로를 수정할 수 없으며 일부 find구현에서는 다른 콘텐츠와 연결할 수 없으며 {}일부는 다중 패스도 지원하지 않지만 {}변환을 수행할 수 있는 셸과 같은 일부 명령은 항상 실행할 수 있습니다.

find ./tests/ -name '*.in' -type f -exec sh -c '
  ret=0
  for file do
    myprog -i "$file" -o "${file%.*}.out" || ret="$?"
  done
  exit "$ret"' sh {} +

myprog위에서는 직접 실행하는 대신 sh발견된 파일의 경로와 함께 일부 인라인 코드를 실행하고 전달합니다( 가능한 한 많은 파일을 전달하는 대신) {} +.{} ';'

sh이러한 파일을 차례로 반복하고 확장자 제거 myprog와 같은 변환을 적용한 후 호출합니다 .${file%.*}

주변 따옴표를 참고하세요 *.in. 이것이 없으면 명령을 실행하는 셸은 패턴을 그대로 전달하는 대신 find이름이 로 끝나는 현재 디렉터리의 파일 목록으로 확장하려고 시도합니다 ..infind

sh위에서 호출이 실패 하면 myprog실패한 종료 상태로 종료한다고 말했습니다 . 실패는 의 종료 상태에 반영되므로 find필요한 경우 조치를 취하거나 errexit이 옵션이 활성화된 경우 스크립트를 종료할 수 있습니다. 하지만 첫 번째 실패 시 중단하는 것은 불가능합니다 myprog.

쉘을 사용하는 경우 zsh내부적으로 찾을 수도 있습니다.

set -o errexit
for file (./tests/**/*.in(ND.)) myprog -i $file -o $file:r.out

첫 번째 실패 시 종료되며 어휘 순서로 목록을 처리합니다( oN이 순서를 비활성화하려면 언제든지 glob 한정자를 추가할 수 있습니다).

find또 다른 접근 방식은 파일을 인쇄하고 변환을 수행하는 일부 명령에 파이프한 다음 에 파이프하는 것입니다 xargs.

find ./tests/ -name '*.in' -type f -print0 |
  gawk -v RS='\0' -v ORS='\0' -v OFS='\0' '
    {
      filein = fileout = $0; sub(/\.in$/, ".out", fileout)
      print "-i", filein, "-o", fileout
    }' | xargs -r0 -n4 myprog

호출이 실패하면 xargs0이 아닌 종료 상태가 다시 반환됩니다. myprogGNU는 해당 옵션을 사용하여 여러 호출을 병렬로 실행할 xargs수 있습니다 .-P

또는 perl후처리하여 실행하도록 할 수도 있습니다.

find ./tests/ -name '*.in' -type f -print0 |
  perl -l -0ne '
    system("myprog", "-i", $_, "-o", s/\.in\Z/.out/r) == 0 or
      $ret = 1;
    END {exit $ret}'

쉘을 find설정하지 않는 한 (지원되는 경우) 메소드의 사후 처리된 출력은 실패 종료 상태(있는 경우)(예: 특정 디렉토리에 들어갈 수 없는 경우)를 마스킹합니다.pipefail

파이프를 사용하면 myprog표준 입력 내용에도 영향을 줍니다(예: 사용자에게 메시지를 표시해야 하는 경우). GNU는 표준 입력을 열고 일부 다른 xargs메소드는 그대로 유지됩니다. 이는 / 의 파이프가 될 것임을 의미합니다./dev/nullperlfindgawk

답변2

당신은 훌륭한 답변을 받았습니다. 그러나 귀하의 질문의 전제는 그것이 무엇이어야 하는가가 아니라고 생각합니다. bash에서 루프를 작성하는 것을 두려워해서는 안 되며, 여전히 다른 유틸리티에 주의해야 한다는 점을 고려하면 이 경우 bash를 사용하지 않을 이유가 없습니다.

이 예에서는 다음과 같이 간단히 수행해도 아무런 문제가 없습니다.

for file in test/*; do 
    [[ -e "$file" ]] || continue
    echo cp "$file" "${file/test/tests_20220615}.out"; 
done
cp test/1.in test_20220615/1.out
cp test/10.in test_20220615/10.out
cp test/2.in test_20220615/2.out
cp test/3.in test_20220615/3.out
cp test/4.in test_20220615/4.out
cp test/5.in test_20220615/5.out
...

이는 기본 nullglob동작에 관계없이 작동합니다. 특히, 파일을 확인하는 조건을 추가하고( [[ -e "$i" ]]해당 이름을 가진 파일이 존재하는 경우 true) 출력이 확실하지 않은 경우 거기에 echo 문을 던져(더 나은 경우 printf) 모든 것이 올바른지 확인하세요.

관련 정보