Bash의 C 스타일 for 루프에서 "while IFS= read -r line" 시뮬레이션

Bash의 C 스타일 for 루프에서 "while IFS= read -r line" 시뮬레이션

먼저, 이 문제의 배경을 소개하겠습니다. while IFS= read -r line; do ... done < input.txt많이 알려진 구조 입니다파일을 한 줄씩 읽기쉘 스크립트에서. 하지만 C 스타일 for 루프로 작업하는 동안(여기 일부 사용자가 모르는 경우 for((i=0;i<=$val;i++))do;...done및 에서 사용되는 유형입니다) C와 유사한 언어의 while 및 for 루프는 상호 교환이 가능하며 다른 루프를 시뮬레이션할 수 있다는 것을 기억했습니다. . 그래서 위에서 언급한 구조를 시뮬레이션하기 위해 C 스타일 루프를 생각해 냈습니다.bashkshwhile 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. forwhileC에서는 쉽게 변환할 수 있지만 동일한 상황이 셸에서는 적용되지 않습니다.

(C에서도 for루프의 구조는 계산 루프를 의미한다고 말하고 싶지만 물론 완전히 명확하지는 않습니다.)

이것에 관해서는:

기술적으로 여기에는 i 변수가 포함된 "내장" 행 카운터도 있습니다.

나는 그것에 내장된 것이 아무것도 없다고 생각합니다. 행 카운터를 수동으로 초기화하고 수동으로 증가시켰습니다. 보다 전통적인 루프를 사용하여 while동일한 작업을 수행 할 수 있습니다.

i=0
while IFS= read -r line; do 
    ... 
    let i++
done < input.txt

(또는 i=$((i + 1))더 휴대 가능)

답변2

@ilkkachu님 말씀에 전적으로 동의합니다.

read그러나 FWIW, 해당 명령을 조건(해당 구문의 출처)의 일부로 사용하려면 다음 규칙을 사용할 수 있습니다.forksh93for ((...))

function read.get {
  IFS= read -r line
  .sh.value=$(($? == 0))
}

for ((i = 0; read; i++)) {
  printf '%5d: %s\n' "$i" "$line"
}

명령이 확장 시 실행되고 성공 또는 실패 시 1로 확장되도록 변수 에 get대한 규칙을 설정했습니다 .$readread$readread0

또는 다음의 변형을 사용하세요.유형:

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.valueread

또는 ${ ...; }명령 대체 형식:

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).

관련 정보