먼저, 이 문제의 배경을 소개하겠습니다. while IFS= read -r line; do ... done < input.txt
많이 알려진 구조 입니다파일을 한 줄씩 읽기쉘 스크립트에서. 하지만 C 스타일 for 루프로 작업하는 동안(여기 일부 사용자가 모르는 경우 for((i=0;i<=$val;i++))do;...done
및 에서 사용되는 유형입니다) C와 유사한 언어의 while 및 for 루프는 상호 교환이 가능하며 다른 루프를 시뮬레이션할 수 있다는 것을 기억했습니다. . 그래서 위에서 언급한 구조를 시뮬레이션하기 위해 C 스타일 루프를 생각해 냈습니다.bash
ksh
while IFS= read -r line
for((i=1;;i++))
do
IFS= read -r line || break
# do something with line here
done < input.txt
\n
여러 유형의 입력(일반 줄, 선행 탭/공백이 있는 줄, 끝나지 않는 줄( 마지막 줄을 캡처할 수 없는 경우)) 으로 테스트해 보았는데 모든 경우에 작동합니다. 둘 다 완전히 유효 read
하고 루프 방식과 동일합니다 while
. 기술적으로 여기에는 변수가 있는 "내장" 행 카운터도 있습니다 i
.
따라서 질문은 이 접근 방식을 사용하지 말아야 할 이유가 있습니까(Python 및 Python을 제외한 다른 쉘로 이식할 수 없는 것 외에 ksh
) 있습니까? bash
가능한 실패가 있습니까? 이 접근 방식은 잘 작동하지만 내 스크립트에서 이 접근 방식을 적극적으로 사용하기 전에 제가 간과하고 있는 문제가 있는지 알고 싶습니다.
답변1
이 접근법을 사용하지 말아야 할 이유가 있습니까?
명확성 또는 부족함.
while
이와 같은 루프는 다음 과 같이 while sometest ; do ...
작성할 수도 있습니다 .
while :; do
if ! sometest; then
break
fi
...
그러나 우리는 (쉘이나 C에서) 이것을 하지 않습니다. 왜냐하면 루프 조건을 사람들이 찾는데 익숙한 곳에서 멀리 이동시키기 때문입니다. 구성은 비슷합니다. 구성의 중간 표현식을 비워 둡니다 for (( ; ; ))
.
물론 for (( ; ; ))
셸에서는 산술 표현식만 평가되기 때문에 이 작업을 수행해야 합니다 read
. for
및 while
C에서는 쉽게 변환할 수 있지만 동일한 상황이 셸에서는 적용되지 않습니다.
(C에서도 for
루프의 구조는 계산 루프를 의미한다고 말하고 싶지만 물론 완전히 명확하지는 않습니다.)
이것에 관해서는:
기술적으로 여기에는 i 변수가 포함된 "내장" 행 카운터도 있습니다.
나는 그것에 내장된 것이 아무것도 없다고 생각합니다. 행 카운터를 수동으로 초기화하고 수동으로 증가시켰습니다. 보다 전통적인 루프를 사용하여 while
동일한 작업을 수행 할 수 있습니다.
i=0
while IFS= read -r line; do
...
let i++
done < input.txt
(또는 i=$((i + 1))
더 휴대 가능)
답변2
@ilkkachu님 말씀에 전적으로 동의합니다.
read
그러나 FWIW, 해당 명령을 조건(해당 구문의 출처)의 일부로 사용하려면 다음 규칙을 사용할 수 있습니다.for
ksh93
for ((...))
function read.get {
IFS= read -r line
.sh.value=$(($? == 0))
}
for ((i = 0; read; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
명령이 확장 시 실행되고 성공 또는 실패 시 1로 확장되도록 변수 에 get
대한 규칙을 설정했습니다 .$read
read
$read
read
0
또는 다음의 변형을 사용하세요.유형:
typeset -T read_t=(
typeset value
function get {
IFS= read -r _.value
((.sh.value = $? == 0))
}
)
read_t line
for ((i = 0; line; i++)) {
printf '%5d: %s\n' "$i" "${line.value}"
}
여기서 type은 확장될 때 행으로 읽혀지고 , 성공하면 1로 확장되고 , 그렇지 않으면 0으로 확장되는 read_t
객체입니다 .theobject.value
read
또는 ${ ...; }
명령 대체 형식:
for ((i = 0; ${ IFS= read -r line; echo "$(($? == 0))";}; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
와 함께 zsh
, 납치하다동적으로 이름이 지정된 디렉터리특징:
set -o extendedglob
handle_-read:var()
case $1:$2 in
(d:-read:[a-zA-Z_][a-zA-Z0-9_]#)
IFS= read -r ${2#*:} && reply=('' $#2);;
(*) false;;
esac
zsh_directory_name_functions+=(handle_-read:var)
for ((i = 0; ${#${(D):--read:line}} == 3; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
(제가 이것을 추천하는 것은 아닙니다).
이것이 산술 표현식의 일부로 명령을 실행할 수 있는 유일한 방법입니다.아니요서브쉘에서.
일반 명령 대체( $(...)
또는 `...`
)를 사용하여 산술 표현식 내에서 명령을 실행할 수도 있지만 이는 하위 쉘에서 수행되므로 다음과 같습니다.
for ((i = 0; $(IFS= read -r line; echo "$((!$?))"); i++)) {
printf '%5d: %s\n' "$i" "$line"
}
이는 에 대한 유효한 구문이지만 서브셸 외부에서 변수를 설정할 bash
수는 없습니다 .$line
그러나 을 사용하면 zsh
다음을 수행할 수 있습니다.
for ((i = 0; ${${line::=$(IFS= read -re)}+$?} == 0; i++)) {
printf '%5d: %s\n' "$i" "$line"
}
그러나 이는 각 라인에 대해 하위 쉘을 분기하고 추가 파이프를 통해 라인을 공급하기 때문에 비효율적입니다.
bash
무조건 할당 매개변수 확장 연산자 가 없으며 ${var::=value}
중첩 매개변수 확장 기능이 매우 제한됩니다. 여기에는 Bourne 연산자(이전에 설정되지 않은 경우 값 할당)가 있으며 ${var=value}
중첩을 허용하는 일부 연산자가 있습니다. ${foo#${bar}}
예를 들어 다음과 같이 할 수 있습니다.
unset line
for ((i = 0; 0*${?#"x${line=`IFS= read -r && printf %s "$REPLY"`}"}+$? == 0; i++)); do
printf '%5d: %s\n' "$i" "$line"
unset line
done
(여기서 해결해야 함두 가지 오류bash
).