POSIX 방식으로 문자열 변수의 줄 수를 계산하는 방법은 무엇입니까?

POSIX 방식으로 문자열 변수의 줄 수를 계산하는 방법은 무엇입니까?

Bash에서 이 작업을 수행할 수 있다는 것을 알고 있습니다.

wc -l <<< "${string_variable}"

기본적으로 내가 찾은 모든 것은 <<<Bash 연산자와 관련이 있습니다.

하지만 POSIX 셸에서는 <<<정의되지 않았으며 몇 시간 동안 해결 방법을 찾지 못했습니다. 나는 간단한 해결책이 있다고 확신하지만 불행히도 지금까지 해결책을 찾지 못했습니다.

답변1

간단한 대답은 wc -l <<< "${string_variable}"입니다 printf "%s\n" "${string_variable}" | wc -l.

<<<실제로 파이프는 다르게 작동합니다. <<<명령에 대한 입력으로 전달하기 위해 임시 파일이 생성되는 반면 |파이프는 생성됩니다. bash 및 pdksh/mksh(ksh93 또는 zsh 제외)에서 파이프 오른쪽의 명령은 하위 쉘에서 실행됩니다. 그러나 이 특별한 경우에는 이러한 차이점이 중요하지 않습니다.

줄 수 측면에서 이는 변수가 비어 있지 않고 개행 문자로 끝나지 않는다고 가정합니다. 변수가 명령 대체의 결과인 경우 개행으로 끝나지 않으므로 대부분의 경우 올바른 결과를 얻지만 빈 문자열의 경우 1을 얻습니다.

var=$(somecommand); wc -l <<<"$var"와 사이에는 두 가지 차이점이 있습니다 somecommand | wc -l. 명령 대체와 임시 변수를 사용하면 출력의 마지막 줄이 개행으로 끝나는지 여부를 잊어버리고 후행 공백 줄이 제거됩니다(명령이 비어 있지 않은 유효한 텍스트 파일을 출력하는 경우 항상 그렇습니다). 출력이 비어 있으면 하나를 추가하십시오. 결과 행과 개수 행을 모두 유지하려면 알려진 텍스트를 추가하고 끝 부분에서 제거하면 됩니다.

output=$(somecommand; echo .)
line_count=$(($(printf "%s\n" "$output" | wc -l) - 1))
printf "The exact output is:\n%s" "${output%.}"

답변2

쉘 내장 기능을 따르지 않으며 POSIX 호환 옵션 grep과 같은 외부 유틸리티를 사용합니다.awk

string_variable="one
two
three
four"

grep줄의 시작 부분과 일치하려면 with를 사용하세요.

printf '%s' "${string_variable}" | grep -c '^'
4

그리고awk

printf '%s' "${string_variable}" | awk 'BEGIN { count=0 } NF { count++ } END { print count }'

일부 GNU 도구(특히 GNU)는 POSIX 버전의 도구를 실행하는 옵션을 grep고려하지 않습니다 . POSIXLY_CORRECT=1변수 설정으로 인해 영향을 받는 유일한 동작은 grep명령줄 플래그가 처리되는 순서입니다. 문서(GNU grep매뉴얼)에 따르면

POSIXLY_CORRECT

설정되면 grep은 POSIX 요구 사항에 따라 작동합니다. 그렇지 않으면 grep다른 GNU 프로그램처럼 작동합니다. POSIX에서는 파일 이름 뒤에 오는 옵션이 기본적으로 파일 이름으로 처리되어야 하며 이러한 옵션은 피연산자 목록 앞에 정렬되어 옵션으로 처리됩니다.

바라보다grep에서 POSIXLY_CORRECT를 사용하는 방법은 무엇입니까?

답변3

Here-string은 <<<here-document의 거의 한 줄 버전입니다 <<. 전자는 표준 기능이 아니지만 후자는 표준 기능입니다. <<이 경우에는 . 다음은 동일해야 합니다.

wc -l <<< "$somevar"

wc -l << EOF
$somevar
EOF

그러나 변수에 5줄만 있음에도 불구하고 $somevar둘 다 끝에 추가 개행 문자를 추가합니다 .6

s=$'foo\n\n\nbar\n\n'
wc -l <<< "$s"

다음을 사용하여 printf추가 개행이 필요한지 결정할 수 있습니다 .

printf "%s\n" "$s" | wc -l         # 6
printf "%s"   "$s" | wc -l         # 5

그러나 wc완전한 줄(또는 문자열의 개행 수)만 계산됩니다. grep -c ^마지막 줄 조각도 계산되어야 합니다.

s='foo'
printf "%s" "$s" | wc -l           # 0 !

printf "%s" "$s" | grep -c ^       # 1

${var%...}(물론 루프에서 한 번에 한 줄씩 제거하기 위해 확장을 사용하여 셸의 줄 수를 완전히 계산할 수도 있습니다...)

답변4

놀라울 정도로 자주 발생하는 상황에서 실제로 해야 할 일은 모든 것을 처리하는 것입니다.비어 있지 않음어떤 방식으로든(계수 포함) 변수 내에 있는 줄의 경우 IFS를 개행 문자로 설정한 다음 쉘의 단어 분리 메커니즘을 사용하여 비어 있지 않은 줄을 구분할 수 있습니다.

예를 들어, 다음은 제공된 모든 인수에서 비어 있지 않은 줄을 합산하는 작은 셸 함수입니다.

lines() (
IFS='
'
set -f #disable pathname expansion
set -- $*
echo $#
)

여기서는 중괄호 대신 괄호를 사용하여 함수 본문의 복합 명령을 구성합니다. 이렇게 하면 함수가 호출될 때마다 외부 세계의 IFS 변수 및 경로 이름 확장 설정을 오염시키지 않도록 하위 쉘에서 실행됩니다.

비어 있지 않은 줄을 반복하려면 다음을 수행하십시오.

IFS='
'
set -f
for line in $lines
do
    printf '[%s]\n' $line
done

이러한 방식으로 IFS를 운영하는 것은 탭으로 구분된 열 형식 입력에 공백이 포함될 수 있는 경로 이름을 구문 분석하는 것과 같은 작업에도 유용하며 종종 간과되는 기술입니다. 그러나 일반적으로 space-tab-newline의 IFS 기본 설정에 포함된 공백 문자를 의도적으로 제거하면 일반적으로 볼 것으로 예상되는 위치에서 단어 분리가 비활성화될 수 있다는 점을 알아야 합니다.

예를 들어 변수를 사용하여 이와 같은 복잡한 명령줄을 작성하는 경우 변수가 비어 있지 않은 것으로 설정된 경우에만 포함 할 ffmpeg수 있습니다 . 일반적으로 를 사용하여 이를 달성할 수 있지만 , 이 매개변수 확장을 완료할 때 IFS가 일반적인 공백 문자를 포함하지 않는 경우 및 사이의 공백은 단어 구분 기호로 사용되지 않고 모두 단일 매개변수로 전달되므로 이해하지 못합니다. .-vf scale=$scalescale${scale:+-vf scale=$scale}-vfscale=ffmpeg-vf scale=$scale

이 문제를 해결하려면 확장을 수행하기 전에 IFS가 더 정상적으로 설정되었는지 확인하거나 ${scale}두 가지 확장을 수행해야 합니다. ${scale:+-vf} ${scale:+scale=$scale}명령줄의 초기 구문 분석 중에 쉘이 수행하는 단어 분할(수행되는 단어 분할과 비교) 해당 명령줄 처리의 확장 단계 중) 분할(반대)은 IFS에 의존하지 않습니다.

이런 종류의 작업을 수행하려는 경우 시간을 투자할 가치가 있는 다른 방법은 탭과 줄 바꿈만 포함하는 두 개의 전역 셸 변수를 만드는 것입니다.

t=' '
n='
'

$t이렇게 하면 모든 코드에 따옴표로 묶인 공백을 추가하지 않고도 탭과 줄 바꿈이 필요한 확장에 및 를 포함할 수 있습니다 . $n다른 메커니즘 없이 POSIX 셸에서 공백을 완전히 인용하지 않으려는 경우 printf이것이 도움이 될 수 있습니다. 단, 명령 확장 시 후행 줄 바꿈을 제거하는 문제를 해결하기 위해 약간의 조정이 필요합니다.

nt=$(printf '\n\t')
n=${nt%?}
t=${nt#?}

때로는 IFS를 각 명령의 환경 변수로 설정하는 것이 효과적일 때도 있습니다. 예를 들어, 다음은 탭으로 구분된 입력 파일의 각 줄에서 공백과 배율 인수를 허용하는 경로 이름을 읽는 루프입니다.

while IFS=$t read -r path scale
do
    ffmpeg -i "$path" ${scale:+-vf scale=$scale} "${path%.*}.out.mkv"
done <recode-queue.txt

이 경우 read내장 함수는 IFS를 탭으로 설정하므로 공백에서 읽는 입력 줄도 분할하지 않습니다. 하지만IFS=$t set -- $lines 아니요작동 중: $lines내장 인수를 작성할 set때 쉘이 확장됩니다.앞으로명령이 실행되기 때문에 내장된 기능 자체가 실행되는 동안에만 적용되는 방식으로 임시로 IFS를 설정하기에는 너무 늦습니다. 이것이 위에서 제공한 코드 조각이 IFS를 별도의 단계로 설정하고 IFS 유지를 처리해야 하는 이유입니다.

관련 정보