Bash에서 큰따옴표가 있는 배열과 따옴표가 없는 배열의 차이점은 무엇입니까?

Bash에서 큰따옴표가 있는 배열과 따옴표가 없는 배열의 차이점은 무엇입니까?

쉘스크립트에서 버그를 추적하는 동안 이 코드 조각에서 다음 동작을 발견했습니다.

declare -a filelist
readarray filelist < <(ls -A)
readonly filelist
for file in "${filelist[@]}"; do
  sha256sum ${filelist[$file]} | head -c 64
done

배열이 filelist큰따옴표로 묶이지 않으면 명령이 성공합니다. 저는 코딩을 개선하기 위해 ShellCheck를 사용해 왔으며 다음과 같은 제안을 받았습니다.

큰따옴표는 와일드카드와 단어 분리기를 방지합니다.

이 경우에는 단어 분할에 대해 걱정하지 않지만 다른 많은 경우에는 걱정하기 때문에 코드 일관성을 유지하려고 노력합니다. 그러나 배열을 큰따옴표로 묶으면 명령이 실패합니다. 코드를 단일 요소로 줄이면 다음과 같은 결과가 나타납니다.

bash-5.0# sha256sum ${filelist[0]} | head -c 64
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855

bash-5.0# sha256sum "${filelist[0]}" | head -c 64
sha256sum: can't open 'file1
': No such file or directory

분명히... 큰따옴표를 사용할 수 없습니다. 이 경우 단어 분리는 문제가 되지 않기 때문입니다. 하지만 앞으로도 그럴 수도 있을 것 같아서 포스팅을 하고 싶었습니다.

내 질문은 두 부분으로 구성됩니다.

  1. 위의 큰따옴표 배열 외에 단어 분할을 방지하는 "모범 사례" 방법이 있습니까?
  2. 배열의 작은따옴표는 어디에서 왔나요? 편집: 작은따옴표가 없습니다. 작은따옴표는 열 수 없는 파일 이름을 표시하는 오류입니다.

또한 궁금해서 echo ${filelist[0]}포함되지 않은 추가 개행 문자가 포함되는 이유는 무엇입니까 echo "${filelist[0]}"?

답변1

참조 배열 확장에는 전혀 문제가 없습니다.

물론, 결과를 알고 받아들인다면 인용하지 않아도 괜찮습니다. 따옴표가 없는 확장자는 분할 및 와일드카드의 영향을 받습니다. 그리고 코드에서 ${filelist[…]}IFS 문자 제거(문자열에 <space>, <tab>또는 가 포함된 경우 분할 <newline>)를 수행해야 합니다.

이것이 인용되지 않은 확장이 수행하는 작업이며, 후행 <newline>.

무엇만들다문제는 를 사용할 때 readarray각 배열 요소에서 후행 구분 기호를 제거하지 않는다는 것입니다. 이렇게 하면 오류 메시지에 반영된 후행이 유지됩니다
.<newline>

사용할 수 있는 것은 다음과 같습니다.

readarray -t filelist < <(ls -A)

-t옵션은 각 파일 이름에서 후행 줄 바꿈을 모두 제거합니다.

-t 읽은 각 줄에서 후행 구분 기호(기본 줄 바꿈)를 제거합니다.


그러나 코드에는 다른 문제가 있습니다.

  • 배열을 선언하거나 지울 필요가 없습니다 filelist. 이는 기본적으로 readarray에 의해 수행됩니다. 이는 다른 상황에서도 필요합니다.

  • 구문 분석된 출력이 필요하지 않습니다 ls. 사실 이는 나쁜 생각입니다. 배열의 파일 목록을 얻는 가장 간단한 방법은 다음과 같습니다.

    filelist=( ./* )
    

    그리고 더 나은 결과를 얻으려면 디렉터리를 사용하지 않는 것이 좋습니다.

    for file in ./*; do
      [[ -f $file ]] && filelist+=( "$file" )
    done
    
  • 루프 내에서는 $filevar 값을 사용해야 합니다.

    for file in "${filelist[@]}"; do
      sha256sum "$file" | head -c 64
    done
    

    for file in "${!filelist[@]}"; do어떤 목록을 사용하지 않는 한열쇠정렬.

  • 전체 목록을 처리하면 됩니다.하나sha256sum에 전화하세요.

    sha256sum "${filelist[@]}" | cut -c -64
    

개선된 스크립트는 다음과 같습니다.

filelist=()              # declare filelist as an array and empty it.
for file in ./*; do
    if [[ -f $file ]]; then
        filelist+=( "$file" )
    fi
done
declare -r filelist      # declare filelist as readonly.
sha256sum "${filelist[@]}" | cut -c -64

답변2

이 경우에는 분사에 대해 걱정하지 않습니다

글쎄요, 사실 당신은그것에 의지하다배열 항목에서 후행 줄 바꿈을 제거하십시오!

배쉬의 readarray( mapfile)구분 기호는 기본적으로 유지됩니다. 매뉴얼 페이지나 명령줄 도움말에는 명확하게 나와 있지 않은 것 같지만 옵션이 있습니다.제거하다구분 기호는 기본적으로 제거되지 않습니다.

-t     Remove a trailing delim (default newline) from each line read.

따라서 배열의 실제 문자열은 다음과 같습니다.file1[newline]

따옴표가 없으면 단어 분리는 후행 공백을 제거하여 개행을 수정합니다. 그러나 파일 이름에 공백이 포함되어 있으면 토큰화로 인해 평소처럼 공백이 엉망이 됩니다. 큰따옴표로 묶은 배열은 이를 방지합니다. 첫 번째 질문에 답하려면,모범 사례는 큰따옴표입니다., 여기에는 필요하지 않은 추가 개행 문자가 있습니다.

$@( 약간 혼란스러운 예외는 큰따옴표로 묶인 배열 입니다. 여기서 큰따옴표로 묶인 문자열은 각 배열 요소에 대해 하나씩 여러 단어를 생성합니다.)

명령줄 ${filelist[$file]}에서도 사용할 수 있습니다 . sha256sum이것은 작동하지 않습니다. file인덱스가 아닌 배열에서 받은 값이 이미 포함되어 있습니다.

최소한의 수정으로 다음과 같이 작동할 수 있습니다.

declare -a filelist
readarray -t filelist < <(ls -A)
readonly filelist
for file in "${filelist[@]}"; do
    sha256sum "$file" | head -c 64
done

declare(나는 명시적인 것도 실제로 필요하다고 생각하지 않습니다 .)


ls위의 문제는 본 내용과 관련이 없습니다 .파일 이름을 한 줄에 하나씩 파일에 저장하고 이 옵션을 사용하지 않고 readarray/를 사용하여 mapfile파일을 읽는 경우에도 -t동일한 문제가 발생합니다 . (또는 의 출력을 읽는 경우 find, 이 경우에는 사용할 수 있습니다 find -exec.)

물론, 이것은쓸모없는 사용 ls의 일부 버전 은 ls출력에서 ​​파일 이름을 손상시킬 수 있습니다. (내 생각에는 GNU ls가 파이프로 출력할 때 이 작업을 수행하지 않는 것 같습니다.)

Bash에서는 배열을 glob으로 채울 수 있습니다.

shopt -s dotglob
filelist=(*)
for file in *; do ...

또는 배열에 저장하지 않고 glob에서 루프를 실행합니다.

shopt -s dotglob
for file in *; do ...

꼭 필요하다는 점 참고하세요shopt -s dotglob*몇 가지 서류를 얻으려면, 쉘에 따라 다릅니다.

답변3

코드 조각을 기반으로 한 문제 중 하나는 구문 분석 중일 수 있습니다 ls. 이는 위험하고 수많은 문제로 가득 차 있으므로 피하는 것이 가장 좋습니다.

대신에

declare -a filelist
readarray filelist < <(ls -A)
readonly filelist
for file in "${filelist[@]}"; do

더 간단하고 안전합니다!

for file in *; do

이 경우:

for file in *; do
  sha256sum "${file}" | head -c 64
done

readarray또한 줄바꿈을 포함하여 호출할 때 전달된 리터럴 데이터를 보존하는 데 도움이 됩니다. 따라서 인용된 값을 에코하면 개행 문자가 유지됩니다. 인용하지 않으면 쉘은 이를 토큰 간 공백으로 무시합니다. 이것이 sha256sum실패의 이유이기도 합니다. 이름이 지정된 파일이 있는 경우 foo해당 파일에 해당하지 않는 readarray값을 전달하십시오 . foo\n일부 변수 값이 실수로 삭제되었기 때문에 이를 역참조하면 문제가 "수정"됩니다.

관련 정보