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
이름이 로 끝나는 현재 디렉터리의 파일 목록으로 확장하려고 시도합니다 ..in
find
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
호출이 실패하면 xargs
0이 아닌 종료 상태가 다시 반환됩니다. myprog
GNU는 해당 옵션을 사용하여 여러 호출을 병렬로 실행할 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/null
perl
find
gawk
답변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
) 모든 것이 올바른지 확인하세요.