와일드카드와 일치하는 파일이 없는 파일을 처리하는 sh 구문은 무엇입니까?

와일드카드와 일치하는 파일이 없는 파일을 처리하는 sh 구문은 무엇입니까?

/bin/sh와일드카드와 일치하는 모든 파일을 처리하는 쉘 스크립트를 작성하고 싶습니다 . 하나 이상의 일치하는 파일을 처리하는 것은 쉽습니다. 그러나 일치하는 파일이 0개인 경우를 처리하는 것이 어색합니다.

명백한 구성은 다음과 같습니다.

#!/bin/sh
for f in *.ext; do
  handle "$f"
done

where는 *.ext쉘이 파일 경로와 비교하는 하나 이상의 표현식일 수 있으며, handle기존 파일에 대한 경로가 제공되면 올바르게 실행되지만 파일에 매핑되지 않는 경로를 제공하면 실패하는 쉘 명령입니다. (이런 의문을 불러일으키는 경우에는 *.flacand 인데 ffmpeg그건 상관없다고 생각합니다.)

일치하는 파일이 있는 경우 이 스크립트는 다음을 수행합니다 foo.ext.bar.ext

handle "foo.ext"
handle "bar.ext"

예상대로. 그러나 만약 거기에~ 아니다일치하는 파일이 있으면 이 스크립트는 다음과 같은 오류 메시지를 표시합니다.

handle: *.ext: No such file or directory

나는 왜 이런 일이 일어나는지 이해한다고 생각합니다.세게 때리다매뉴얼 페이지(또한 유효하다고 가정 /bin/sh) "뒤에 오는 단어 목록이 in확장되어 항목 목록이 생성됩니다.… 다음 항목을 확장하면 빈 목록이 생성되고 명령이 실행되지 않으며 반환 상태는 다음과 같습니다. 0." 분명히 *.ext"확장"되면 결과 목록 *.ext에 빈 목록 대신 이 포함됩니다. 그러나 그것은 그런 일이 일어나는 것을 멈추는 방법을 설명하지 않습니다.

(업데이트:sh (1)Heirloom Project의 매뉴얼 페이지sh이것은 Bourne Again Shell이 ​​아닌 Bourne Shell을 설명합니다 bash. 아래에파일 이름 생성, "패턴과 일치하는 파일 이름이 없으면 단어는 변경되지 않은 상태로 유지됩니다."라고 명확하게 표시됩니다. 목록에 패턴을 sh남기는 이유를 설명합니다 .)*.ext

일치하는 파일이 없으면 오류 없이 0회 실행되지만 일치하는 각 파일에 대해 한 번 실행되도록 이 루프를 작성하는 간결하고 관용적인 방법은 무엇입니까? 더 좋은 점은 다음과 같은 여러 패턴에서 작동하는 것입니다.

for f in *.ext1 special.ext1 *.ext2; do ...

나는 /bin/sh이식성을 위해 와 호환되는 구문을 사용하는 것을 선호합니다. 저는 Mac OS X 10.11.6을 사용하고 있지만 모든 Unix 계열 OS에서 작동하는 구문을 원합니다.

나는 그러한 루프를 작성하는 몇 가지 서투르고 관용적이지 않은 방법을 생각해 냈습니다. 즉시 좋은 답변을 얻지 못하면 기록을 위해 답변으로 제공하겠습니다.

답변1

이를 수행하는 가장 간단한 이식 가능한 방법은 확장이 실제로 존재하는 것을 생성하지 않는 경우 루프를 건너뛰는 것입니다.

for f in *.ext1 *.ext2; do
  [ -e "$f" ] || continue
  handle "$f"
done

설명: .ext2 파일은 여러 개 있지만 .ext1 파일은 없다고 가정합니다. 이 경우 와일드카드는 *.ext1 filea.ext2 fileb.ext2 filec.ext2. 따라서 [ -e "*.ext1" ]실패하고 실행되며 continue나머지 루프 반복을 건너뜁니다. 그런 다음 [ -e "filea.ext2" ]등이 성공하고 루프 반복이 정상적으로 실행됩니다.

그런데, 예를 들어 [ -f "$f" ] || continue일반 파일이 아닌 항목을 건너뛰도록 이를 수정할 수 있습니다(디렉토리 등을 건너뛰려는 경우).

물론 bash에서 실행 중인 경우 shopt -s nullglob목록에 일치하지 않는 패턴이 남지 않도록 먼저 실행하면 됩니다. 그런 다음 shopt -u nullglob예상치 못한 부작용을 방지하기 위해 나중에 (예: 설정된 grep pattern *.nonexistent경우 stdin에서 읽으려고 시도합니다 ).nullglob

답변2

/bin/bashBourne Shell( )에서 Bourne Again Shell( )로 전환하면 /bin/sh간단한 해결이 가능해집니다.

그만큼bash(1)매뉴얼 페이지를 언급한다널글로브옵션:

설정된 경우 bash는 파일과 일치하지 않는 패턴을 허용합니다(참조경로명 확장위) 자체가 아닌 널 문자열로 확장합니다.

그만큼경로명 확장섹션은 다음과 같이 말합니다:

단어 분할 후 ...bash는 각 단어에서 문자를 검색합니다.*,?, 그리고[. 이러한 문자 중 하나가 나타나면 해당 단어는 패턴으로 간주되고 패턴과 일치하는 알파벳순으로 정렬된 파일 이름 목록으로 대체됩니다. 일치하는 파일 이름이 없고 쉘 옵션이널글로브활성화되지 않은 경우 단어는 변경되지 않습니다. 만약널글로브옵션이 설정되었지만 일치하는 항목이 없으면 단어가 제거됩니다.…

그래서, 설정널글로브옵션은 패턴에 대해 원하는 동작을 제공합니다. 패턴과 일치하는 파일이 없으면 루프가 실행되지 않습니다.

#!/bin/bash
shopt -s nullglob
for f in *.ext; do
  handle "$f"
done

하지만널글로브옵션은 다른 명령의 원하는 동작을 방해할 수도 있습니다. 따라서 기존 설정을 저장하는 것이 현명할 것입니다.널글로브, 나중에 복원하십시오. 다행스럽게도 shopt -p내장 명령은 입력으로 재사용할 수 있는 형식으로 출력을 내보냅니다. 하지만 모든 옵션에 대한 출력을 내보냅니다. 따라서 grep다음을 선택하는 데 사용하세요.nullobj환경. 아래 로그를 참조하십시오(여기서는 $bash 명령 프롬프트를 나타냄).

$ shopt -p | grep nullglob
shopt -u nullglob
$ shopt -s nullglob
$ shopt -p | grep nullglob
shopt -s nullglob

따라서 와일드카드 패턴과 일치하는 0개 이상의 파일을 처리하는 최종 bash 구문은 다음과 같습니다.

#!/bin/bash
SAVED_NULLGLOB=$(shopt -p | grep nullglob)
shopt -s nullglob
for f in *.ext; do
  handle "$f"
done
eval "$SAVED_NULLGLOB"

또한 질문에는 다음과 같은 여러 패턴이 언급되었습니다.

for f in *.ext1 special.ext1 *.ext2; do ...

그만큼널글로브옵션은 "패턴"이 아닌 목록의 단어에 영향을 주지 않으므로 special.ext1여전히 루프에 전달됩니다. 이에 대한 유일한 해결책은@고든 데이비슨continue표현, [ -e "$f" ] || continue.

인정: 둘 다@이그나시오 바스케스-아브람스그리고@고든 데이비슨암시된 것과 bash(1)그것의널글로브옵션. 감사합니다. 그들은 완벽하게 작동한 예제를 사용하여 즉시 답변을 제공하지 않았기 때문에 제가 직접 그렇게 하고 있습니다.

관련 정보