긴 이야기 짧게

긴 이야기 짧게

Git을 사용하여 디렉토리의 파일에 대한 모든 단계되지 않은 수정 사항의 bash 배열을 얻으려고 합니다. 다음 코드는 디렉터리에서 수정된 모든 파일을 인쇄합니다.

git -C $dir/.. status --porcelain | grep "^.\w" | cut -c 4-

이 인쇄

"Directory Name/File B.txt"
"File A.txt"

나는 사용하려고

arr1=($(git status --porcelain | grep "^.\w" | cut -c 4-))

그러나 이후에

for a in "${arr1[@]}"; do echo "$a"; done

${arr1[@]}(주변에 따옴표가 있거나 없이 인쇄됨)

"Directory
Name/File
B.txt"
"File
A.txt"

나도 시도했다

git -C $dir/.. status --porcelain | grep "^.\w" | cut -c 4- | readarray arr2

그러나 이후에

for a in "${arr2[@]}"; do echo "$a"; done

(따옴표 포함 및 제외 ${arr2[@]}) 아무것도 인쇄하지 않습니다. 미리 사용해도 declare -a arr2아무런 효과가 없습니다.


내 질문은: 이 값을 배열로 어떻게 읽을 수 있습니까? (이것은 내 아르고스 플러그인에서 사용됩니다.Git 바, 혹시라도 내 코드를 모두 볼 수 있도록).

답변1

긴 이야기 짧게

배쉬에서:

readarray -t arr2 < <(git … )
printf '%s\n' "${arr2[@]}"

귀하의 질문에는 두 가지 다른 질문이 있습니다

  1. 껍질이 깨졌습니다.
    이 작업을 수행할 때:

    arr1=($(git … ))
    

    "명령 확장"은 따옴표가 없으므로 쉘 분할 및 glob의 영향을 받습니다.

    쉘 분할이 수행하는 작업을 정확히 보려면 ​​printf를 사용하십시오.

    $ printf '<%s>  '  $(echo word '"one simple sentence"')
    <word>  <"one>  <simple>  <sentence">
    

    이는 다음을 통해 피할 수 있습니다.인용하다:

    $ printf '<%s>  '  "$(echo word '"one simple sentence"')"
    <word "one simple sentence">
    

    그러나 이것은 또한 원하는 개행 분할을 방지합니다.

  2. 파이프라인
    실행 시:

    git … | … | … | readarray arr2
    

    배열 변수 arr2가 설정되었지만, |파이프()가 닫히면 사라집니다.

    마지막 서브셸에 머무르는 경우 다음 값을 사용할 수 있습니다.

    $ printf '%s\n' "First value." "Second value." | 
            { readarray -t arr2; printf '%s\n' "${arr2[@]}"; }
    First value.
    Second value.
    

    arr2그러나 파이프라인을 떠난 후에는 그 가치가 유지되지 않습니다.

해결책

줄 바꿈을 분할하려면 읽기를 사용해야 하지만 파이프는 사용할 수 없습니다.
오래된 것부터 새로운 것까지:

  1. 반지 모양.
    배열이 없는 오래된 쉘의 경우(위치 인수가 있는 유일한 준 배열):

    set --
    while IFS='' read -r value; do
        set -- "$@" "$value"
    done <<-EOT
    $(printf '%s\n' "First value." "Second value.")
    EOT
    
    printf '%s\n' "$@"
    

    배열 설정(ksh, zsh, bash)

    i=0; arr1=()
    while IFS='' read -r value; do
        arr1+=("$value")
    done <<-EOT
    $(printf '%s\n' "First value." "Second value.")
    EOT
    
    printf '%s\n' "${arr1[@]}"
    

  2. Here-string here document() 대신 here-string()을 사용할 수 있습니다 .<<<<<

    i=0; arr1=()
    while IFS='' read -r value; do
        arr1+=("$value")
    done <<<"$(printf '%s\n' "First value." "Second value.")"
    
    printf '%s\n' "${arr1[@]}"
    
  3. 프로세스 대체
    이를 지원하는 셸(ksh, zsh, bash)에서 이를 사용하여 <( … )여기에서 문자열을 바꿀 수 있습니다.

    i=0; arr1=()
    while IFS='' read -r value; do
        arr1+=("$value")
    done < <(printf '%s\n' "First value." "Second value.")
    
    printf '%s\n' "${arr1[@]}"
    

    차이점은 <( )NUL 바이트를 내보내는 기능인 반면, 여기서 문자열은 NUL을 삭제(또는 경고)할 수 있습니다. 기본적으로 여기에서는 후행 개행 문자가 문자열에 추가됩니다. AFAIK 다른 것이 있을 수도 있습니다.

  4. readarray는 다음 용도로
    사용됩니다 .readarraybash[ㅏ](일명 mapfile) 루프를 피하세요:

    readarray -t arr2 < <(printf '%s\n' "First value." "Second value.")
    printf '%s\n' "${arr2[@]}"
    

[ㅏ]ksh에서는 사용하기 전에 변수를 지우는 를 사용해야 read -A하지만 개행 문자를 분할하고 전체 입력을 한 번에 읽으려면 약간의 "마법"이 필요합니다.

IFS=$'\n' read -d '' -A arr2 < <(printf '%s\n' "First value." "Second value.")

필요할 것이예요mapfilezsh에서 모듈 로드비슷한 일을 해보세요.

답변2

readarray로 파이프하면 arr2 배열을 올바르게 채우는 하위 쉘이 시작된 다음 종료됩니다. readarray에 대한 입력으로 프로세스 대체를 사용합니다.

readarray -t arr2 < <(git ...)

답변3

당신은 가깝습니다

이것은 "공백 문자가 포함된 파일 이름" 문제입니다.

기본적으로 구분 기호는 공백 문자입니다. 이는 IFS환경 변수에 의해 설정됩니다.

환경 변수를 임시로 변경하려면 다음 명령을 사용하십시오.

ifs_backup=$IFS
IFS=$(echo -en "\n\b")

그런 다음 이 명령의 출력은 다음과 같습니다.

for a in "${arr1[@]}"; do echo "$a"; done

할 것이다:

"Directory Name/File B.txt"
"File A.txt"

복구 IFS:

IFS=$ifs_backup

답변4

코드 내부 부분에 따옴표를 추가합니다.

arr1=("$(git status --porcelain | grep "^.\w" | cut -c 4-)")

관련 정보