빈 디렉토리에 대한 이식성 검사

빈 디렉토리에 대한 이식성 검사

Bash와 Dash를 사용하면 셸을 사용하여 빈 디렉터리를 확인할 수 있습니다(단순화를 위해 도트 파일은 무시).

set *
if [ -e "$1" ]
then
  echo 'not empty'
else
  echo 'empty'
fi

그러나 나는 최근 이 경우 Zsh가 실패한다는 것을 알게 되었습니다.

% set *
zsh: no matches found: *

% echo "$? $#"
1 0

따라서 명령이 set실패할 뿐만 아니라 설정도 되지 않습니다 $@. $#is 인지 테스트할 수 있다고 생각했지만 0Zsh가 실행을 중단한 것 같습니다.

% { set *; echo 2; }
zsh: no matches found: *

Bash 및 Dash와 비교:

$ { set *; echo 2; }
2

bash, dash 및 zsh에서 작동하는 방식으로 이 작업을 수행할 수 있습니까?

답변1

이것에는 몇 가지 문제가 있습니다

set *
if [ -e "$1" ]
then
  echo 'not empty'
else
  echo 'empty'
fi

암호:

  • nullglob옵션이 활성화되면(이전에는 대부분의 다른 쉘에서 지원됨) 모든 쉘 변수(및 일부 쉘의 함수)가 대신 나열됩니다.zshset *set
  • 숨김이 아닌 첫 번째 파일 이름이 -또는 로 시작하는 경우 +옵션으로 처리됩니다 set. zsh 및 ksh 의 일부 구현/버전에서는 +A[$(reboot)].set -- *
  • *숨겨지지 않은 파일만 확장되므로 디렉터리가 비어 있는지 테스트하는 대신 디렉터리에 숨겨지지 않은 파일이 포함되어 있는지 테스트합니다. 일부 셸의 경우 dotglob또는 옵션을 사용 하거나 특수 변수(셸에 따라)를 globdot사용하여 문제를 해결할 수 있습니다.FIGNORE
  • [ -e "$1" ]stat()시스템 호출이 성공했는지 테스트합니다 . 첫 번째 파일이 액세스할 수 없는 위치에 대한 심볼릭 링크인 경우 false가 반환됩니다. 디렉토리가 비어 있는지 확인하기 위해 파일이 필요하지 않습니다 stat()(또는 필요하지도 않음 lstat()). 내용이 있는지 확인하기만 하면 됩니다.
  • *확장 프로그램은 현재 디렉터리를 열고, 모든 항목을 검색하고, 숨겨지지 않은 모든 항목을 저장하고 정렬해야 하는데, 이는 역시 매우 비효율적입니다.

디렉토리가 비어 있지 않은지( 및를 제외한 모든 항목이 있는지) 확인하는 가장 효율적인 방법 은 glob 한정자를 사용하는 .것입니다 ( for..zshFF가득한, 여기서는 비어 있지 않음을 의미합니다):

if [ .(NF) ]; then
  print . is not empty
else
  print "it's empty or I can't read it"
fi

Nnullglob글로벌 예선 입니다 . 따라서 가득 차면 .(NF)확장되고 , 그렇지 않으면 아무것도 없습니다...

lstat()디렉토리에 있는 후 zsh링크 수가 2보다 큰 것으로 확인되면 하위 디렉토리가 하나 이상 있으므로 비어 있지 않으므로 디렉토리를 열 필요조차 없습니다. 이 경우 읽기 권한이 없어도 디렉토리가 비어 있지 않음을 알 수 있습니다. 그렇지 않으면 zsh는 디렉토리를 열고 해당 내용을 읽은 후 모든 내용을 읽거나 저장하거나 정렬 .하지 않고 둘 중 하나도 아닌 첫 번째 항목에서 중지합니다...

POSIX 쉘( zsh에뮬레이션에서 POSIX적으로만 작동 sh)을 사용하면 glob을 사용하여 디렉토리가 비어 있지 않은지 확인하는 것은 매우 어색합니다.

한 가지 방법은 다음과 같습니다.

set .[!.]* '.[!.]'[*] .[.]?* [*] *
if [ "$#$1$2$3$4$5" = '5.[!.]*.[!.][*].[.]?*[*]*' ]; then
  echo "empty or can't read"
else
  echo not empty
fi

(Glob 관련 옵션이 기본값에서 변경되지 않고(POSIX만 지정 ), (for ) 및 (for) 변수가 noglob설정되지 않고 (for) 유효한 문자를 형성하지 않는 바이트 시퀀스가 ​​포함된 파일 이름이 없다고 가정합니다. ).GLOBIGNOREbashFIGNOREkshyash

POSIX 셸에서 glob이 일치하지 않으면 확장되지 않는다는 아이디어입니다(이것은 1970년대 후반 Bourne 셸에 도입된 버그 기능이었습니다). 따라서 ==를 set -- *얻으면 일치하는 항목이 없기 때문인지, 아니면 이름이 존재하는 파일이 있는지 알 수 없습니다 .$1**

문제를 해결하는 (결함 있는) 방법은 을 사용하는 것입니다 [ -e "$1" ]. set -- [*] *파일이 없으면 위의 파일이 남아 있고 숨겨진 파일 [*] *에 대해 유사한 작업을 수행하기 *때문에 두 경우를 명확하게 합니다. 이는 Bourne 쉘의 또 다른 결함( Forsyth 쉘, pdksh 및 pdksh에 의해 수정됨 ) * *으로 인해 다소 당혹스럽습니다. 확장에는 특수(의사) 항목이 포함되어 있으며 보고 될 때 .zshfish.*...readdir()

따라서 모든 쉘에서 작동하게 하려면 다음을 수행할 수 있습니다.

cwd_empty()
  if [ -n "$ZSH_VERSION" ]; then
    eval '! [ .(NF) ]'
  else
    set .[!.]* '.[!.]'[*] .[.]?* [*] *
    [ "$#$1$2$3$4$5" = '5.[!.]*.[!.][*].[.]?*[*]*' ]
  fi

그럼에도 불구하고, zsh기본 구문은 POSIX sh 구문과 호환되지 않습니다. 이는 Bourne 셸(POSIX.2가 처음 출시되기 오래 전)의 대부분의 주요 문제를 이전 버전과 호환되지 않는 방식으로 수정하기 때문입니다 *. Bourne 셸 이전의 셸에는 이 문제가 없었 csh으며 tcsh그렇지 fish않았습니다. .*포함하고 있지만 .분할 +glob과 같은 여러 다른 항목은 인수 또는 산술 확장에 대해 수행하므로 에뮬레이션을 열지 않는 한 ..POSIX sh로 작성된 코드를 기대할 수 없습니다. 항상 작동합니다.zshsh

시뮬레이션 sh은 특별히 존재하므로 에서 할 수 있습니다 zsh.

POSIX에서 파일에 쓰려면 source다음과 같이 하면 됩니다.shzsh

emulate sh -c 'source ./that-file'

그런 다음 파일은 시뮬레이션에서 평가되며 sh파일에 선언된 모든 기능은 해당 시뮬레이션 모드로 유지됩니다.

답변2

zsh의 기본 동작은 오류를 발생시키는 것이지만,이는 nomatch옵션에 의해 제어 됩니다.. 이 옵션을 설정 해제 *하여 bash 및 dash와 같이 그대로 둘 수 있습니다 .

setopt -o nonomatch

이 명령은 다른 명령에는 영향을 미치지 않지만 무시할 수 있습니다.

setopt -o nonomatch 2>/dev/null || true ; set *

이는 zsh에서 실행되며 다른 명령에서 실패한 명령에 대한 setopt오류 출력( ) 및 반환 코드( )를 억제합니다.2>/dev/null|| true

작성된 대로 다음과 같은 파일이 있는 경우 명령이 실패하면 종료하도록 셸 옵션을 -e실행 set -e하고 변경합니다. 창의력을 발휘하면 결과가 더 나빠질 것입니다. set -- *더욱 안전해지고 옵션 변경을 방지할 수 있습니다.

답변3

Zsh의 구문은 sh와 호환되지 않습니다. sh처럼 보일 만큼 가깝지만 sh 코드를 수정하지 않고 실행할 수 있을 만큼 가깝지는 않습니다.

예를 들어 sh용으로 작성된 sh 함수 또는 코드 조각이 있고 이를 zsh 스크립트에서 사용하려는 경우 zsh에서 sh 코드를 실행하려는 경우 다음을 사용할 수 있습니다.emulate내장. 예를 들어, zsh 스크립트에서 sh에 대해 작성된 파일을 얻으려면 다음을 수행하십시오.

emulate sh -c 'source /path/to/file.sh'

sh 구문을 사용하여 함수나 스크립트를 작성하고 zsh에서 실행하려면 시작 부분에 배치하세요.

emulate -L sh 2>/dev/null || true

sh 구문에서 zsh는 모든 POSIX 구성을 지원합니다( bash --posixksh93 또는 mksh와 마찬가지로 POSIX와 호환됩니다). 또한 배열(ksh, bash 및 ksh에서는 0-인덱싱 emulate sh되지만 기본 zsh에서는 1-인덱싱됨) 및 와 같은 일부 ksh 및 bash 확장을 지원합니다 [[ … ]]. emulate … ksh …bash emulate … sh …(이것은 스크립트/함수에 고유한 것이 아닙니다).if [[ -n $BASH ]]; then shopt -s extglob; fi

.및를 제외하고 디렉토리의 모든 항목을 열거하는 기본 zsh 방법 ..은 다음과 같습니다.

set -- *(DN)

이는 다음을 사용합니다.글로벌 예선 D도트 파일을 포함하고 N일치하는 항목이 없으면 빈 목록을 생성합니다.

.및를 제외하고 디렉토리의 모든 항목을 열거하는 이식 가능한 sh 방법은 ..훨씬 더 복잡합니다. 도트 파일을 나열해야 하며, 현재 디렉터리의 파일을 나열하거나 절대 경로가 보장되지 않는 경우 파일 이름이 대시로 시작하는 경우 주의해야 합니다. 확장되지 않은 패턴을 ..?* .[!.]* *제외하고 패턴이 있는 모든 파일을 나열하고 .제거 하는 방법은 다음과 같습니다 ...

set -- ..?*
if [ "$#" -eq 1 ] && ! [ -e "$1" ] && ! [ -L "$1" ]; then shift; fi
set -- .[!.]* "$@"
if [ "$#" -eq 1 ] && ! [ -e "$1" ] && ! [ -L "$1" ]; then shift; fi
set -- * "$@"
if [ "$#" -eq 1 ] && ! [ -e "$1" ] && ! [ -L "$1" ]; then shift; fi

디렉토리가 비어 있는지 테스트하고 싶다면 더 쉬운 방법이 있습니다.

if ls -A /path/to/directory/ | grep -q '^'; then
  echo "/path/to/directory is not empty"
else
  echo "/path/to/directory is empty"
fi

답변4

해결책이 무엇인지 잘 모르겠습니다 set *. 이건 어때?

dir="./empty"
has_file=false

for i in "$dir"/*; do 
  if test ! "$i" = "$dir/*"; then
    has_file=true
    echo "$i"
  if
done

echo "$has_file"

테스트를 거쳤으며 이는 zsh, bash, ash, ksh, AKA에 bourne적합합니다.heirloom

관련 정보