쉘 파일 이름 확장은 목록의 (*) 항목을 어떻게 구분합니까?

쉘 파일 이름 확장은 목록의 (*) 항목을 어떻게 구분합니까?

나는 아직 쉘 확장을 완전히 이해하지 못합니다(언젠가는 이해할 수 있기를 바랍니다)...
이 댓글을 보았습니다 .슈퍼유저 문제, 그런데 아직 길가에 주차되어 있는 것 같아요...

쉘 없이 Linux를 사용하는 것은 시내 교통에서 페라리를 시속 50km로 운전하는 것과 같습니다. 모든 즐거움이 사라질 것입니다…

다음 예를 이해하지 못합니다. 두 번째 예제 "배열 항목 수:"가 첫 번째 예제와 달라지는 계층 구조 또는 다른 요인은 무엇입니까?

쉘에 "공간"이 도입되면 어떻게 되나요? 아니면 echo공백을 도입하고 쉘이 (아마도) \0을 사용합니까?

#!/bin/bash
# Make a couple of files whose names contain a space.
junkd=$HOME/junkd
mkdir $junkd # || exit 1
cd $junkd
touch f\ {1..2}
#
echo -n * |xxd         # This shows a space between the two names.
names=$(echo -n * )
echo -n "$names" |xxd  # This shows a space between the two names.
#
# So far, it seems that the shell is inserting a space between each filename.
#
array=( $names )
echo "array item count: ${#array[@]}" 
# 4 items... This shows that a space is the delimiter char ....
#
array=( * )
echo "array item count: ${#array[@]}" 
# 2 items... What happened to the shell introduced space?
#

답변1

쉘 명령(보다 정확하게는 "간단한 명령")은 단어 목록으로 구성됩니다. 각 단어는 임의의 문자열일 수 있습니다. 쉘 단어에는 공백과 구두점이 포함될 수 있습니다.

를 실행하면 echo -n *셸은 경로 이름 확장(파일 이름 생성 또는 와일드카드라고도 함)을 수행 *하고 이를 일치하는 파일 이름 목록으로 바꿉니다. 따라서 확장 후 이 명령은 , echo, -n4 f 1개의 단어로 구성됩니다 f 2. 이 명령은 echo두 개의 인수로 실행되며 인수 사이에 공백이 있는 인수를 인쇄합니다( -n옵션으로 인해 종료되는 개행 문자는 없습니다). 따라서 출력은 f 1 f 2.연습: 두 개의 연속 공백으로 구성된 이름을 가진 다른 파일을 만들고 실행한 echo -n *후 출력을 이해했는지 확인하세요.

이를 실행하면 names=$(echo -n * )명령의 출력이 names변수에 저장됩니다. 여기서 라인은 와 같습니다 names='f 1 f 2'.

이제 갑니다 array=( $names ). 이는 배열 할당이지만 이 경우 확장에는 영향을 주지 않습니다. 따옴표 없는 변수 확장 이므로 $names단어 분할을 한 다음 경로명 확장을 수행합니다. 토큰화는 변수 값(문자열)이 공백의 각 시퀀스에서 여러 부분으로 분할됨을 의미합니다( IFS정확한 규칙은 셸 문서를 검색하세요). 0개, 1개 이상의 단어로 끝날 수 있습니다. 여기서 문자열은 , 및 4개의 단어 f로 분할 1됩니다 . 따라서 배열에는 4개의 요소가 포함됩니다(각 요소는 한 문자 단어임).f2연습: 이름에 두 개의 연속 공백이 포함된 추가 파일에 대한 배열의 정확한 내용은 무엇입니까?

다음으로, 당신은 그것을 시도했습니다 array=( * ). 여기에는 일반적인 확장이 적용되는 배열의 단어가 있으며 마지막 단어는 경로 이름 확장입니다. 일치하는 파일이 두 개 있으므로 배열에는 각 파일의 이름인 f 1및 이라는 두 단어가 포함됩니다 f 2.

쉘 프로그래밍 실습에 대한 이 분석에서 어떤 조언을 얻을 수 있습니까? 첫째, 일반적인 쉘 프로그래밍 원칙이 있습니다. 특별한 이유가 없는 한 항상 변수 확장 주위에 큰따옴표를 두십시오. 그런 다음 목록을 문자열 변수에 저장하지 마십시오. 파일 이름 목록을 저장하려면 배열에 직접 넣으십시오.

files=(*)
ls -l "${files[@]}"

추가 연습: 단일 별표( touch '*')로 이름이 지정된 파일을 만들고 이 명령을 다시 실행합니다. 출력을 이해하시나요?

또한 zsh는 변수 확장을 위해 토큰화 또는 ​​경로 이름 확장을 수행하지 않습니다. 이는 프로그래밍을 더욱 스마트하게 만듭니다.

답변2

~에서남자 난교

확장 확장은 단어로 분할된 후 명령줄에서 수행됩니다. 수행되는 확장 유형에는 중괄호 확장, 물결표 확장, 매개변수 및 변수 확장, 명령 대체, 산술 확장, 토큰화, 경로 이름 확장 등 7가지 유형이 있습니다. 확장 순서는 중괄호 확장, 물결표 확장, 매개변수, 변수 및 산술 확장, 명령 대체(왼쪽에서 오른쪽으로 수행), 토큰화 및 경로 이름 확장입니다.

array=( $names )

이것이 4개의 항목을 제공하는 이유는 인용되지 않은 $names인수가 다음에 의해 추가로 영향을 받기 때문입니다.분사IFS기본 내부 필드 구분 기호를 기준으로 합니다 <space><tab><newline>. 단어 분할을 비활성화하기 위해 인용을 한다면 "$names", value 를 가진 배열 요소를 얻게 될 것입니다 f 1 f 2. 이는 다시 원하는 것이 아닙니다.

array=( * )

반면에 위의 내용은 다음의 경우에만 적용됩니다.경로명 확장이것은 마지막으로 실행된 확장입니다. 밝혀지다아니요단어 분할을 수행하여 필요한 2가지 요소를 얻으세요.

이것이 작동하도록 하려면 array=( $names )파일 이름에도 포함되지 않은 공백이 아닌 문자를 사용하여 파일 이름을 어떻게든 분리해야 합니다. 그런 다음 IFS를 해당 문자로 설정해야 합니다.

$ names=$(echo f* | sed "s/ /#/2")
$ echo $names
f 1#f 2
$ IFS='#' array=( $names )
$ echo ${#array[@]}
2
$ echo ${array[0]}
f 1

더 우아한 접근 방식은 NUL 바이트를 \0파일 이름 구분 기호로 사용하는 것입니다. 왜냐하면 NUL 바이트는 파일 이름의 일부가 되지 않는다는 것이 보장되기 때문입니다. 이를 달성하려면 플래그가 find있는 명령 -print0readNUL로 구분된 내장 명령을 사용해야 합니다 . 또한 공간에 대한 토큰화가 수행되지 않도록 IFS를 지워야 합니다.

#!/bin/bash

unset array

while IFS= read -r -d $'\0' name; do
  array+=( "$name" )
done < <(find . -type f -name "f*" -print0 )

고쳐 쓰다

확장은 단어로 분할된 후 명령줄에서 수행됩니다.

요점을 더 설명하기 위해 사람들이 위의 인용문으로 인해 얼마나 혼란스러워할지 상상할 수 있습니다.분사마지막에서 두 번째로 발생하는 확장입니다.

제 생각에는 인용문을 표현하는 더 좋은 방법은 다음과 같습니다.

분할 후 명령줄에서 확장 논쟁.

쉘에서 인수 분할은 다음과 같습니다.언제나공백으로 완성되면 이러한 매개변수는 더욱 확장됩니다. 매개변수에 공백을 포함하려면 다음을 사용해야 합니다.인용하다또는도망가다. IFS매개변수 분할은 향상되지 않고 단어 분할만 향상됩니다.

다음 예를 고려하십시오.

$ touch f{1,2}; IFS="#"; rm f1#f2
rm: cannot remove `f1#f2': No such file or directory

로 설정해도 쉘이 여전히 하나의 인수만 볼 수 있다는 사실은 바뀌지 않으며 IFS, 다양한 확장에 의해 추가로 영향을 받습니다.#f1#f2

직접 사용해보시길 적극 추천드려요배쉬 FAQ아직하지 않았다면. 특히 다음 두 가지 보충 항목을 읽어볼 것을 강력히 권장합니다.

  1. 논쟁
  2. 분사

관련 정보