POSIX- 문자열 변수의 줄 수를 세는 방법은 무엇입니까?


10

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

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

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

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

답변:


11

간단한 대답은 wc -l <<< "${string_variable}"ksh / bash / zsh 바로 가기입니다 printf "%s\n" "${string_variable}" | wc -l.

실제로 <<<파이프 작업 방식 과 차이가 있습니다. <<<명령에 입력으로 전달되는 임시 파일을 |작성하는 반면 파이프를 작성합니다. bash 및 pdksh / mksh (ksh93 또는 zsh는 아님)에서 파이프 오른쪽의 명령이 서브 쉘에서 실행됩니다. 그러나 이러한 차이는이 특별한 경우에 중요하지 않습니다.

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

이 둘 사이의 차이입니다 var=$(somecommand); wc -l <<<"$var"somecommand | wc -l: 명령 치환을 사용하여이 임시 변수가 떨어진 끝에 빈 줄, 출력의 마지막 줄은 줄 바꿈에 종료 여부 잊어 스트립 (명령이 유효한 비어 있지 않은 텍스트 파일을 출력하는 경우에는 항상 않습니다) 출력이 비어 있으면 1을 초과합니다. 결과와 카운트 라인을 모두 유지하려면 알려진 텍스트를 추가하고 마지막에 제거하여 수행 할 수 있습니다.

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

1
@Inian Keep wc -l은 원본과 정확히 동일 <<<$foo합니다. 값 이 비어 $foo도 값에 개행을 추가합니다 $foo. 나는 이것이 왜 원했던 것이 아닐지에 대한 대답으로 설명하지만 그것이 요청 된 것입니다.
Gilles 'SO- 악마 그만해

2

같은 외부 유틸리티를 사용하여 내장 기능을 쉘에 부합하지 않음 grepawkPOSIX 호환 옵션,

string_variable="one
two
three
four"

와 이렇게하면 grep라인의 시작과 일치합니다

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

그리고 awk

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

일부 GNU 도구, 특히 GNU grepPOSIXLY_CORRECT=1POSIX 버전의 도구를 실행하는 옵션을 고려하지 않습니다 . 에서는 grep변수 설정에 의해 영향을받는 유일한 동작 명령 행 플래그의 처리 순서의 차이 일 것이다. 문서 (GNU grep매뉴얼)에서

POSIXLY_CORRECT

설정되면 grep은 POSIX가 요구하는대로 작동합니다. 그렇지 않으면 grep다른 GNU 프로그램처럼 동작합니다. POSIX에서는 파일 이름 뒤에 오는 옵션을 파일 이름으로 취급해야합니다. 기본적으로 이러한 옵션은 피연산자 목록의 앞에 순열되며 옵션으로 처리됩니다.

grep에서 POSIXLY_CORRECT를 사용하는 방법을 참조하십시오 ?


2
분명히 wc -l여전히 여기에서 가능합니까?
마이클 호머

@MichaelHomer : 내가 관찰 한 바에 wc -l따라, 적절한 개행으로 구분 된 스트림이 필요합니다 (마지막으로 계산하려면 '\ n`이 끝남). 간단한 FIFO를 사용하여 사용할 수 없습니다. printf예를 들어 printf '%s' "${string_variable}" | wc -l예상대로 작동하지 않지만 herestring이 추가 된 <<<후행 \n으로 인해 발생합니다.
Inian

1
printf '%s\n'당신이 그것을 꺼내기 전에 그 일 을하고 있었다 ...
마이클 호머

1

here-string <<<은 here-document의 한 줄 버전입니다 <<. 전자는 표준 기능이 아니지만 후자는 표준 기능입니다. <<이 경우에도 사용할 수 있습니다 . 이들은 동일해야합니다.

wc -l <<< "$somevar"

wc -l << EOF
$somevar
EOF

변수가 다섯 줄만 있더라도 둘 다 끝에 줄 바꿈을 추가합니다 ( $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%...}루프에서 한 번에 하나씩 확장을 사용하여 확장을 사용하여 셸에서 줄을 완전히 계산할 수도 있습니다 ...)


0

놀랍게도 빈번하게 필요한 경우 변수 내에서 비어 있지 않은 모든 행을 계산하는 방법을 포함하여 어떤 방식으로 처리하는 것입니다 (계산 포함). 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를 조작하는 것은 종종 간과되는 기술이며, 탭으로 구분 된 열 입력의 공백을 포함 할 수있는 경로 이름 구문 분석과 같은 작업에도 유용합니다. 그러나 IFS의 기본 설정 space-tab-newline에 일반적으로 포함되어있는 공백 문자를 일부러 제거하면 일반적으로 예상되는 위치에서 단어 분할이 비활성화 될 수 있습니다.

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

이를 수정하려면 ${scale}확장을 수행하기 전에 IFS가보다 정상적으로 설정되었는지 확인 하거나 두 가지 확장을 수행해야 ${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 작성할 때 쉘이 확장 되므로 내장 자체 실행 중에 만 적용되는 방식으로 IFS의 임시 설정이 너무 늦습니다. 이것이 내가 제공 한 코드 스 니펫이 별도의 단계에서 IFS를 설정 한 이유와이를 유지하는 문제를 처리해야하는 이유입니다.set

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.