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에서는 파일 이름 뒤에 오는 옵션이 기본적으로 파일 이름으로 처리되어야 하며 이러한 옵션은 피연산자 목록 앞에 정렬되어 옵션으로 처리됩니다.
답변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=$scale
scale
${scale:+-vf scale=$scale}
-vf
scale=
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 유지를 처리해야 하는 이유입니다.