사용자 입력 한줄을 Ctrl+D까지 한줄씩 읽어 Ctrl+D를 입력한 줄을 포함시키는 방법

사용자 입력 한줄을 Ctrl+D까지 한줄씩 읽어 Ctrl+D를 입력한 줄을 포함시키는 방법

이 스크립트는 사용자 입력을 한 줄씩 가져와 myfunction각 줄에서 실행됩니다.

#!/bin/bash
SENTENCE=""

while read word
do
    myfunction $word"
done
echo $SENTENCE

입력을 중지하려면 을 누른 [ENTER]다음 을 눌러야 합니다 Ctrl+D.

Ctrl+D누른 줄만 끝내고 처리하도록 스크립트를 다시 작성하려면 어떻게 해야 합니까 Ctrl+D?

답변1

이렇게 하려면 한 줄씩 읽어야 하는 것이 아니라 문자별로 읽어야 합니다.

왜? 쉘은 read() 사용자가 입력한 데이터를 읽기 위해 표준 C 라이브러리 함수를 사용할 가능성이 높으며, 함수는 실제로 읽은 바이트 수를 반환합니다. 0을 반환하면 EOF가 발생했음을 의미합니다(설명서 read(2); 참조 man 2 read). EOF는 문자가 아니라 조건, 즉 "더 이상 내용을 읽을 수 없음"이라는 조건이라는 점에 유의하세요.파일 끝.

Ctrl+D보내전송 종료 문자 (EOT, ASCII 문자 코드 4, $'\04'in bash)을 터미널 드라이버에 복사합니다. 이는 read()쉘에 보낼 대기 호출을 보내는 효과가 있습니다.

한 줄에 텍스트를 입력하는 동안 절반을 누르면 Ctrl+D지금까지 입력한 모든 내용이 쉘 1 로 전송됩니다 . 즉 Ctrl+D, 한 줄에 무언가를 두 번 입력하면 첫 번째는 일부 데이터를 보내고 두 번째는 데이터를 보냅니다.아무것도 없다, 호출은 read()0을 반환하고 쉘은 이를 EOF로 해석합니다. 마찬가지로 Enterfollow 를 누르면 Ctrl+D전송할 데이터가 없기 때문에 쉘은 즉시 EOF를 수신합니다.

그렇다면 두 번 입력하는 것을 어떻게 방지할 수 있나요 Ctrl+D?

내가 말했듯이 단일 문자를 읽으십시오. read쉘 내장을 사용하는 경우 입력 버퍼가 있을 수 있으며 read()입력 스트림에서 최대 그만큼의 문자를 읽어야 합니다(대략 16kb 정도). 이는 쉘이 16kb의 입력 청크를 가져온 다음 16kb보다 작은 청크, 0바이트(EOF)를 가져옵니다. 입력 끝(또는 개행 문자 또는 지정된 구분 기호)이 나타나면 제어가 스크립트로 반환됩니다.

단일 문자를 읽는 데 사용하는 경우 read -n 1쉘은 호출 시 단일 바이트 버퍼를 사용합니다. read()즉, 긴밀한 루프에 들어가 문자별로 읽고 각 문자 다음에 제어를 쉘 스크립트로 반환합니다.

유일한 문제 read -n는 터미널을 "원시 모드"로 설정한다는 것입니다. 즉, 문자가 해석 없이 있는 그대로 전송된다는 의미입니다. 예를 들어 를 누르면 Ctrl+D문자열에 리터럴 EOT 문자가 표시됩니다. 그래서 우리는 그것을 확인해야 합니다. 이는 또한 사용자가 스크립트에 제출하기 전에 를 누르 Backspace거나 Ctrl+W(이전 단어 삭제) 또는 Ctrl+U(줄 시작까지 삭제)를 사용하여 줄을 편집할 수 없다는 부작용도 있습니다 .

긴 이야기를 짧게 만들려면:다음은 입력 줄을 읽기 위해 스크립트가 수행해야 하는 마지막 루프입니다 bash. 동시에 사용자는 다음을 눌러 입력을 중단할 수 있습니다 Ctrl+D.

while true; do
    line=''

    while IFS= read -r -N 1 ch; do
        case "$ch" in
            $'\04') got_eot=1   ;&
            $'\n')  break       ;;
            *)      line="$line$ch" ;;
        esac
    done

    printf 'line: "%s"\n' "$line"

    if (( got_eot )); then
        break
    fi
done

이에 대해 너무 자세히 설명하지 않고 다음을 수행합니다.

  • IFS=변수를 지웁니다 IFS. 이것이 없으면 공백을 읽을 수 없습니다. read -N대신 을 사용합니다 read -n. 그렇지 않으면 줄바꿈을 감지할 수 없습니다. 이 -r옵션을 사용 read하면 백슬래시를 올바르게 읽을 수 있습니다.

  • case명령문은 읽은 각 문자( $ch)에 대해 작동합니다. $'\04'EOT()가 감지 되면 got_eot1로 설정되고 break이를 내부 루프에서 꺼내는 명령문이 실행됩니다. 개행 문자( )가 감지되면 $'\n'내부 루프에서 빠져 나옵니다. 그렇지 않으면 line변수 끝에 문자를 추가합니다 .

  • 루프 후에 행은 표준 출력으로 인쇄됩니다. 이것이 를 사용하여 호출하는 것입니다 "$line". EOT를 감지하여 여기에 도달했다면 가장 바깥쪽 루프를 종료합니다.

1cat >file 한 터미널과 다른 터미널에서 실행한 다음 부분 줄을 입력 하고 키를 눌러 출력에서 ​​어떤 일이 일어나는지 확인하여 tail -f file이를 테스트 할 수 있습니다 .catCtrl+Dtail


사용자 의 경우 ksh93: 위 루프는 에서 라인 피드 대신 캐리지 리턴을 읽습니다 ksh93. 이는 에 대한 테스트를 $'\n'에 대한 테스트로 변경해야 함을 의미합니다 $'\r'. 쉘은 또한 이를 ^M.

이 문제를 해결하려면:

stty_saved="$( stty -g )"
stty-echoctl

#여기서 반복하세요. $'\n'은 $'\r'로 대체됩니다.

stty "$stty_saved"

break에서와 정확히 동일한 동작을 얻기 전에 명시적으로 개행 문자를 출력할 수도 있습니다 bash.

답변2

터미널 장치의 기본 모드에서 read()시스템 호출(충분히 큰 버퍼로 호출되는 경우)은 전체 라인이 됩니다. 읽은 데이터가 개행 문자로 끝나지 않는 유일한 경우는 를 누를 때입니다 Ctrl-D.

내 테스트(Linux, FreeBSD 및 Solaris)에서는 read()사용자가 통화 중에 더 많은 내용을 입력하더라도 각 줄은 한 줄만 생성했습니다. read()읽은 데이터에 여러 줄이 포함될 수 있는 유일한 경우는 사용자가 줄 바꿈 Ctrl+VCtrl+J(리터럴 다음 문자 다음에 리터럴 줄 바꿈이 오는 경우(캐리지 리턴을 시간에 맞춰 줄 바꿈으로 변환하는 것과 반대 Enter))을 입력할 때입니다.

그러나 내장 쉘은 read개행 문자나 파일 끝이 나타날 때까지 한 번에 한 바이트씩 읽습니다. 저것파일 끝read(0, buf, 1)0이 반환될 때이며 , 이는 Ctrl-D빈 줄을 누를 때만 발생합니다.

여기서는 많은 읽기 작업을 수행하고 Ctrl-D입력이 개행 문자로 끝나지 않는지 감지해야 합니다.

read내장 함수를 사용하여 이 작업을 수행 할 수는 없지만 sysread내장 함수를 사용하면 수행 할 수 있습니다 zsh.

사용자 입력을 고려하려면 다음을 수행하십시오 ^V^J.

#! /bin/zsh -
zmodload zsh/system # for sysread

myfunction() printf 'Got: <%s>\n' "$1"

lines=('')
while (($#lines)); do
  if (($#lines == 1)) && [[ $lines[1] == '' ]]; then
    sysread
    lines=("${(@f)REPLY}") # split on newline
    continue
  fi

  # pop one line
  line=$lines[1]
  lines[1]=()

  myfunction "$line"
done

foo^V^Jbar레코드당 하나의 레코드가 반환된다고 가정하고 이를 하나의 레코드(개행 포함)로 처리하려는 경우 read():

#! /bin/zsh -
zmodload zsh/system # for sysread

myfunction() printf 'Got: <%s>\n' "$1"

finished=false
while ! $finished && sysread line; do
  if [[ $line = *$'\n' ]]; then
    line=${line%?} # strip the newline
  else
    finished=true
  fi

  myfunction "$line"
done

또는 를 사용하면 자체 고급 줄 편집기를 zsh사용하여 데이터를 입력하고 입력 끝을 나타내는 위젯에 매핑할 수 있습니다.zsh^D

#! /bin/zsh -
myfunction() printf 'Got: <%s>\n' "$1"

finished=false
finish() {
  finished=true
  zle .accept-line
}

zle -N finish
bindkey '^D' finish

while ! $finished && line= && vared line; do
  myfunction "$line"
done

bashPOSIX 쉘 또는 다른 POSIX 쉘을 사용하면 동등한 방법으로 다음을 sysread사용하여 시스템 호출을 dd수행 할 수 있습니다 read().

#! /bin/sh -

sysread() {
  # add a . to preserve the trailing newlines
  REPLY=$(dd bs=8192 count=1 2> /dev/null; echo .)
  REPLY=${REPLY%?} # strip the .
  [ -n "$REPLY" ]
}

myfunction() { printf 'Got: <%s>\n' "$1"; }
nl='
'

finished=false
while ! "$finished" && sysread; do
  case $REPLY in
    (*"$nl") line=${REPLY%?};; # strip the newline
    (*) line=$REPLY finished=true
  esac

  myfunction "$line"
done

답변3

무엇을 요구하는지 잘 모르겠지만 사용자가 여러 줄을 입력한 다음 모든 줄을 전체적으로 처리할 수 있도록 하려면 mapfileEOF가 나타날 때까지 사용자 입력을 허용한 다음 를 사용할 수 있습니다. 각 줄이 배열의 항목인 배열을 반환합니다.

프로그램.sh

#!/bin/bash

myfunction () {
    echo "$@"
}

SENTANCE=''
echo "Enter your input, press ctrl+D when finished"
mapfile input   #this takes user input until they terminate with ctrl+D
n=0
for line in "${input[@]}"
do
    ((n++))
    SENTANCE+="\n$n\t$(myfunction $line)"
done
echo -e "$SENTANCE"

$: bash PROGRAM.sh
Enter your input, press ctrl+D when finished
this is line 1
this is line 2
this is not line 10
# I pushed ctrl+d here

1   this is line 1
2   this is line 2
3   this is not line 10

관련 정보