zsh에서 NUL로 구분된 단어의 첨자 변수 확장

zsh에서 NUL로 구분된 단어의 첨자 변수 확장

file디렉터리(또는 디렉터리)에 있는 모든 파일의 MIME 유형을 식별하는 데이터가 생성되었습니다 . 결과는 왼쪽에 명령이 있고 오른쪽에 MIME 유형이 있는 목록입니다. 기본적으로 두 값(또는 문자열?)은 콜론(:)으로 구분됩니다. NUL 문자를 사용하여 MIME 유형과 명령을 구분하고 싶습니다. 그래서,

files=( ${(f)"$(file --print0 --mime-type **/*)"} )

이것은 작동하는 것 같습니다.

print -l ${files[@]}다음을 제공합니다:

cs:           text/x-shellscript
da:           text/x-shellscript
dns-test:     text/x-shellscript

또는 다음과 print -l ${(V)files[@]}같이 주어진다:

cs^@:           text/x-shellscript
da^@:           text/x-shellscript
dns-test^@:     text/x-shellscript

그러나 첫 번째 항목과 일치시키기 위해 아래 첨자를 사용하는 경우단어, 출력이 첫 번째입니다.특징대신에. 그래서,

for f in "${files[@]}"; do
  print "${f[(pws:\0:)1]}"
done

이것은 첫 번째 것만 인쇄합니다.특징각 배열 요소에 대해( 출력의 각 행별 file):

c
d
d

사양을 생략하여 (ps:\0:)기본 공백 구분을 사용하면 각 요소의 첫 번째 단어를 얻게 됩니다. 그래서,

for f in "${files[@]}"; do
  print "${f[(w)1]}"
done

이것은 작동합니다:

cs
da
dns-test

하지만 저는 공백에 의존하고 싶지 않습니다. NUL과 같은 더 안전한 구분 기호를 사용하고 싶습니다. 나도 첨자를 시도했지만 [(pws:\000:)1]이 역시 첫 번째 첨자만 생성되었습니다.특징.

아래 첨자를 사용할 때 NUL로 구분된 단어를 식별하는 방법은 무엇입니까?

답변1

파일 경로에는 줄 바꿈이 포함될 수 있습니다. 이것이 바로 파일이나 파일 관련 정보를 보고하는 일부 유틸리티에서 줄 바꿈 대신 NUL을 사용하여 레코드를 구분할 수 있도록 허용하는 이유입니다. 0은 파일 경로에 나타날 수 없는 유일한 바이트 값이기 때문입니다.

을 사용하는 것처럼 개행 문자로 출력을 분할하면 ${(f)"$(file...)"}목적이 무산됩니다.

fileNUL을 레코드 구분 기호로 사용하지 않고 대신 NUL을 사용하여 출력 레코드(여전히 느슨하게 정의됨)에서 파일 경로를 구분하기 때문에 조금 이상합니다.

$ ls
':\0:'$'\n'':\1:'  'a'$'\n''b'
$ file --print0 --mime-type -- ./* | sed -n l
./:\\0:$
:\\1:\000: application/x-xz$
./a$
b\000:       application/zip$

따라서 로깅은 <file-path><NUL><colon><some-spaces><mime-type><newline>구문 분석을 불필요하게 더 어렵게 만듭니다(또는 텍스트에 개행 문자가 포함된 경우에는 불가능할 수도 있지만 다행스럽게도 --mime-type그렇지 않아야 합니다).

여기에서 다음을 수행할 수 있습니다.

typeset -A mime_type
file --print0 --mime-type --no-pad --separator '' ./**/* |
  while
    IFS= read -rd $'\0' file &&
      IFS=' ' read -r type
  do
    mime_type[$file]=$type
  done

첫 번째는 NUL을 구분 기호 read로 사용하여 NUL로 구분된 파일 경로를 읽고 -d, 두 번째는 개행으로 구분된 MIME 유형(IFS 처리에 의해 선행 공백이 잘림)을 읽습니다.

하나의 공백이 --no-pad될 것이므로 콜론을 제거하면 됩니다.<some-spaces>--separator ''<file-path><NUL><space><mime-type><newline>

루프에서 우리는 $mime_type연관 배열을 채워서 를 사용하여 주어진 파일의 유형을 얻 $mime_type[./given/file]거나 를 사용하여 주어진 유형의 파일을 나열 할 수 있습니다 print -rC1 -- ${(k)mime_type[(R)text/plain]}.


$scalar[(pws[\0])1]NUL로 분할하지 않는 이유는 다음과 같습니다 .현재 zsh 버전의 버그. 이 동작은 일반적으로 그렇지 않은 경우 zsh가 NUL을 이스케이프할 수 없다는 제안과 동일하므로 $array[(pws[])1]결국 빈 문자열에서 분할하게 됩니다. 이스케이프 기능이 부족하면 0x83에서 0xa2까지의 바이트 값을 포함하는 모든 구분 기호(예: áUTF-8로 인코딩된 0xc3 0xa1)와 같은 다른 구분 기호에 영향을 줍니다.

그렇지 않은 경우 문자(공백이 아닌 다른 버그, 이번에는 문서)로 분할되고 NUL이 기본값이므로 s[separator]다음 을 수행할 수 있지만 (의 약어 )를 사용할 수도 있습니다.$IFS$IFSIFS=$'\0'; first_non_empty_NUL_separated_field=$scalar[(w)1]0ps[\0]매개변수 확장 플래그:

non_empty_NUL_separated_fields=( ${(0)scalar} )
NUL_separated_fields=( "${(0@)scalar}" )

(그런 다음 first=$NUL_separated_fields[1]).

아니면 단숨에 first=${${(0)scalar}[1]}.

${scalar%%pattern}또는 Korn 쉘 연산자를 사용할 수 있습니다 .

before_first_NUL=${scalar%%$'\0'*}

또는 IFS 분할:

IFS=$'\0'
NUL_separated_fields=( $=scalar )

또는 ksh93 스타일 ${param/pattern[/replacement]}:

first=${scalar/$'\0'*}

코드에 대한 몇 가지 의견:

  • 에서 file --print0 --mime-type **/*파일 경로가 로 시작하면 -해당 경로는 으로 처리됩니다 file. 이를 방지하기 위해 이를 변경할 수 있지만 file --print0 --mime-type -- **/*호출 중인 현재 작업 디렉터리에 파일이 있으면 여전히 작동하지 않습니다. -를 사용하면 ./**/*이러한 문제(및 추가 문제)를 모두 피할 수 있습니다 .
  • @, 매개변수 확장 플래그 또는 빈 요소가 제거되는 것을 방지하기 위해 인용할 때만 의미가 있습니다 [@]. $@목록 컨텍스트와 동일 ${files[@]}하며 배열의 null이 아닌 요소로 확장됩니다. Korn과 유사한 셸에서는 null 삭제를 방지하고 분할+글로브를 방지하기 위해 따옴표도 필요합니다. Korn과 유사한 쉘(대부분의 다른 쉘이나 언어와 달리)에도 중괄호가 필요하며 모든 요소가 아닌 인덱스 0의 요소로 개별적으로 확장됩니다.$files$files[@]$files[*]$files
  • 를 사용할 때 print일반적으로 옵션을 사용하고 싶고 -r, 그렇지 않으면 일부 백슬래시 처리를 수행하고 거의 항상 또는 옵션 구분 기호를 print사용하려고 합니다. 그렇지 않으면 명령 주입 취약점이 발생하게 됩니다. ACE 취약점입니다. Korn 쉘(이 내장 기능의 출처)과 마찬가지로 or가 필요합니다 (ksh에서는 필요합니다 ).---print $varprint -r - $varprint -r -- $varprint -r - "$var"
  • print -rC1 -- $listprint -rl -- $list일반적으로 olumn에 r목록 aw를 인쇄하는 것보다 낫습니다. 1 C후자는 인수가 전달되지 않으면 빈 줄을 인쇄하기 때문입니다.

관련 정보