
이 스크립트는 사용자 입력을 한 줄씩 받아 myfunction
모든 줄에서 실행됩니다.
#!/bin/bash
SENTENCE=""
while read word
do
myfunction $word"
done
echo $SENTENCE
입력을 중지하려면 을 누른 [ENTER]
다음 을 눌러야 합니다 Ctrl+D
.
누른 Ctrl+D
줄 로만 끝나고 처리하도록 스크립트를 다시 작성하려면 어떻게 해야 합니까 ?Ctrl+D
답변1
그렇게 하려면 한 줄씩 읽어야 하는 것이 아니라 문자별로 읽어야 합니다.
왜? 쉘은 사용자가 입력하는 데이터를 읽기 위해 표준 C 라이브러리 함수를 사용할 가능성이 높으며 read()
해당 함수는 실제로 읽은 바이트 수를 반환합니다. 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로 해석합니다. 마찬가지로 Enter다음에 를 누르면 Ctrl+D전송할 데이터가 없으므로 쉘은 즉시 EOF를 얻습니다.
그렇다면 두 번 입력하지 않으려면 어떻게 해야 할까요 Ctrl+D?
내가 말했듯이 단일 문자를 읽으십시오. read
쉘 내장 명령을 사용하면 입력 버퍼가 있을 수 있으며 read()
입력 스트림에서 최대 해당 문자 수(약 16kb 정도)를 읽도록 요청합니다. 이는 쉘이 16kb 청크 묶음을 얻은 다음, 16kb 미만의 청크, 0바이트(EOF)를 가져옴을 의미합니다. 입력 끝(또는 개행 문자 또는 지정된 구분 기호)을 만나면 제어가 스크립트로 반환됩니다.
을 사용하여 단일 문자를 읽는 경우 read -n 1
쉘은 에 대한 호출에서 단일 바이트의 버퍼를 사용합니다 read()
. 즉, 문자별로 읽는 긴밀한 루프에 앉아 각 문자 이후에 쉘 스크립트에 제어를 반환합니다.
유일한 문제는 read -n
터미널을 "원시 모드"로 설정한다는 것입니다. 즉, 문자가 해석 없이 있는 그대로 전송된다는 의미입니다. 예를 들어 을 누르면 Ctrl+D문자열에 리터럴 EOT 문자가 표시됩니다. 그래서 우리는 그것을 확인해야 합니다. 이는 또한 사용자가 스크립트에 제출하기 전에 을 누르거나 (이전 단어 삭제) 또는 (줄 시작 부분까지 삭제)를 Backspace사용하여 줄을 편집할 수 없는 부작용도 있습니다. .Ctrl+WCtrl+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
)에 대해 작동합니다. EOT($'\04'
)가 감지되면 1로 설정되고 내부 루프에서 빠져나오는 명령문got_eot
으로 넘어갑니다 .break
개행 문자($'\n'
)가 감지되면 내부 루프에서 빠져 나옵니다. 그렇지 않으면 변수 끝에 문자를 추가합니다line
.루프 후에 행은 표준 출력으로 인쇄됩니다. 를 사용하는 스크립트나 함수를 호출하는 곳입니다
"$line"
. EOT를 감지하여 여기에 도달했다면 가장 바깥쪽 루프를 종료합니다.
1cat >file
한 터미널과 다른 터미널에서 실행하여 이를 테스트 tail -f file
한 다음 에 부분 줄을 입력
cat
하고 를 눌러 의 Ctrl+D출력에서 어떤 일이 발생하는지 확인할 수 있습니다 tail
.
사용자 의 경우 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
내장은 개행 문자나 파일 끝이 나타날 때까지 입력을 한 번에 1바이트씩 읽습니다. 저것파일 끝빈 줄을 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
또는 다른 POSIX 쉘을 사용 하면 접근 bash
방식과 동일하게 시스템 호출을 수행하여 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
나는 당신이 요구하는 것이 무엇인지 명확하지 않지만 사용자가 여러 줄을 입력한 다음 모든 줄을 전체적으로 처리할 수 있도록 하려면 를 사용할 수 있습니다 mapfile
. EOF가 나타날 때까지 사용자 입력을 받은 다음 각 줄이 배열의 항목인 배열을 반환합니다.
PROGRAM.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