나는 썼다아름다운 사용자 정의 bash 프롬프트, 완벽하게 작동합니다. 줄 바꿈으로 구분된 여러 명령을 실행하려고 할 때 문제가 발생합니다. (길이가 길어서 죄송하지만 질문이 명확해지기를 바랍니다)
핵심요약: 커서 위치를 읽으면 stdin
그곳에 있는 모든 데이터가 버려집니다. 데이터는 폐기할 수 없습니다. 조언해 주세요.
예상되는 동작
stdin에서 읽지 않는 장기 실행 명령(예 sleep 5
:)을 실행하고 다음 명령 + 입력(예 ls -lah<enter>
:)을 입력하면 bash가 stdin에서 읽기 시작하고 명령 + Enter를 읽고 실행을 시작합니다( ls -lah
여기서 사례). 나는 이것을 타이핑하기 전에 다음 프롬프트가 나타날 때까지 기다리는 "인내성 상황"이 아닌 "열심히 하는 상황"이라고 부르고 싶습니다. (아래 예)
기본 bash 프롬프트 예
PS1
기본 bash 프롬프트(일명)와 같은 것을 사용하면 PS1='[\u@\h \W]\$ '
예상대로 작동합니다.
기본 환자 프로필:
# wait for new prompt to show up and type new command
[me@machine ~]$ sleep 5; echo 'sleep done!'<enter>
sleep done!
[me@machine ~]$ ls -A /etc/skel<enter>
.bash_logout .bash_profile .bashrc
[me@machine ~]$
기본 비상 상황:
# we immediately start typing our next command to be run after we typed the sleep+echo
[me@machine ~]$ sleep 5; echo 'sleep done!'<enter>
ls -A /etc/skel<enter>
sleep done!
# bash now auto-fills the prompt because it reads it from the tty/stdin
# and immediately runs it (because it ends with a newline):
[me@machine ~]$ ls -A /etc/skel
.bash_logout .bash_profile .bashrc
[me@machine ~]$
맞춤 오류 메시지
맞춤형 환자 프로필
환자는 잘 지내고 있습니다. 여기에는 문제가 없습니다.
맞춤형 비상 상황
문제는 precmd()
함수 시작 부분에 있습니다. (저는 사용하고 있어요이 bash-preexec 후크)
거기에서 터미널에 현재 커서 좌표를 요청하는 함수를 호출하여 마지막 명령 실행의 출력이 1로 끝나지 않은 경우 추가 개행 문자를 인쇄해야 하는지 알 수 있습니다. 다른 SO 게시물에서 이 기능을 얻었습니다.https://stackoverflow.com/a/52944692
이 기능을 비활성화하면 문제가 발생하지 않습니다.
function precmd() {
# must be 1st
previous_command_exit="${?}"
# saves cursore coordinates to 2 variables: _cursor_col, _cursor_row
# If I comment-out the call to _fetch_cursor_position, I can use the `eager situation` as expected.
_fetch_cursor_position
# add extra newline if command did not end with a newline
[[ "${_cursor_col}" -gt 1 ]] && printf "\n"
# ...
}
내 경우에는 다음과 같이 정의됩니다.
_fetch_cursor_position() {
local -a pos
IFS='[;' read -p $'\e[6n' -d R -a pos -rs || echo >&2 "failed with error: $? ; ${pos[*]}"
_cursor_row="${pos[1]}"
_cursor_col="${pos[2]}"
}
이 기능이 작동하는 방식은 다음과 같습니다(더 나은 설명은 이 SO 게시물을 참조하세요).
- 터미널이 커서 위치를 보고하도록 요구하는 이스케이프 시퀀스를
^[[6n
터미널에 인쇄합니다. 이는read
(ab)using 플래그에 의해-p prompt
인쇄 됩니다(참조help read
). - 터미널은
^[[y;xR
터미널에 "입력"하여 커서 위치를 보고합니다. 여기서:
^[[: Esc^[
문자 뒤에 텍스트 가[
y: 위에서부터 줄 번호(9보다 클 수 있음)
x: 위에서부터 왼쪽 아래까지의 열 번호, 1부터 시작 (>9일 수 있음)
R: 리터럴 문자R
read
그런 다음 명령은 값을 구문 분석하고 stdin에서 읽어 배열에 할당합니다pos
.
pos[0]:^[
(우리는 이에 대해 신경 쓰지 않습니다)
pos2:
y 위치 값2: x의 값
가능한 해결책
주요 목적은 이 _fetch_cursor_position
함수가 tty/stdin의 입력 데이터를 읽는 것을 방지하는 것입니다. 이것이 불가능할 수도 있으므로 현재 stdin 값을 저장하고 커서 위치를 읽어 stdin 값을 복원하는 방법이 있을 수 있습니다. 나는 bash가 유망해 보였기 때문에 몇 가지 조사를 했지만 coproc
그것이 어떻게 작동할지는 확신하지 못했습니다.
Bash 리디렉션이 있는 것
나는 bash의 I/O 리디렉션을 사용하여 "고급" 작업을 수행하는 데 해결책이 있다는 강한 느낌을 받았습니다. 그러나 실제로는 >/dev/null
, &>/dev/null
, 2>&1
및 |&
와 같은 파일/서브셸 리디렉션 만 사용했으며 추가 FD를 직접 사용한 적은 없습니다.< <(echo abc)
>myfile.txt
내 마음속에 있는 과정은 이렇다.
0
fd를 새 파일 설명자에 백업합니다 (stdout/1
도?)- 동일한 터미널에서 사용할 수 있도록 터미널(
/dev/tty
/ ?) 에 새 stdin/stout를 엽니다 (물론 커서를 이동하지 않고 위의 모든 작업).$(tty)
- 터미널에 위치를 요청하고 응답을 구문 분석합니다.
- 원본 복원
다른 FD로의 출력 위치
터미널이 0
stdin/ 대신 다른 FD 에 좌표 응답을 쓰고 read
거기에서 읽도록 하세요.
이미 존재하는 표준 입력 데이터에 접근할 수 없는 서브쉘입니다.
_fetch_cursor_position
stdin/stout/stderr을 상속하기 때문에 subshell의 약간 편집된 확장 버전을 사용할 때도 작동하지 않습니다.
_echo_cursor_position() {
local pos
IFS='[;' read -p $'\e[6n' -d R -a pos -rs || echo >&2 "failed with error: $? ; ${pos[*]}"
_cursor_row="${pos[1]}"
_cursor_col="${pos[2]}"
# this line is added, no other changes apart from the name
printf "${_cursor_row} ${_cursor_col}"
}
function precmd() {
# must be 1st
previous_command_exit="${?}"
#_fetch_cursor_position
pos=( $(_echo_cursor_position) )
_cursor_row="${pos[0]}"
_cursor_col="${pos[1]}"
# add extra newline if command did not end with a newline
[[ "${_cursor_col}" -gt 1 ]] && printf "\n"
# ...
}
이 상속 문제가 해결될 수 있다면 아마도 더 간단하고 우아한 솔루션이 될 것입니다.
작동하지 않는 것
1: read
리디렉션 없음
커서 위치는 올바르게 저장되지만 열심히 입력한 명령은 사라집니다.
IFS='[;' read -r -s -p $'\e[6n' -d 'R' __garbage __cursor_col __cursor_row
2: read
두 개의 리디렉션을 활성화합니다./dev/tty
Bash를 올바르게 이해한다면 stdin/stdout(및 stderr)은 기본적으로 tty에 연결되므로 이는 1과 다르지 않아야 하지만 그렇지 않습니다.
IFS='[;' read -r -s -p $'\e[6n' -d 'R' __garbage __cursor_col __cursor_row </dev/tty >/dev/tty
에서 man 1 bash
:
대화식 쉘은 옵션이 아닌 인수(-s가 지정되지 않는 한)와 -c 옵션 없이 시작되는 쉘이며 표준 입력과 오류 모두 터미널에 연결됩니다(isatty(3)에 의해 결정됨) 또는 - 나는 옵션 쉘. Bash가 대화형인 경우 PS1을 설정하고 $-를 포함하여 쉘 스크립트나 시작 파일이 이 상태를 테스트할 수 있도록 합니다.
3:2 위상 재지정 printf
및read
1, 2와 같은 결과
printf $'\e[6n' >/dev/tty
IFS='[;' read -r -s -d 'R' __garbage __cursor_col __cursor_row </dev/tty
4: $(tty)
대신 값을 사용하세요/dev/tty
다시 말하지만, 차이는 없습니다
local tty="$(tty)"
printf $'\e[6n' >"${tty}"
IFS='[;' read -r -s -d 'R' __garbage __cursor_col __cursor_row <"${tty}"
무슨 일이 일어나는지
0: 간단한 솔루션
이런 어리석은 노력을 하지 말고 항상 추가 줄 바꿈을 인쇄하십시오.
완벽주의적인 관점에서 보면 정말 그러고 싶지 않아요.
1: tmux 사용
나는 항상 tmux를 사용하므로 필요할 때만 추가 줄 바꿈만 인쇄할 수 있는 실용적이고 좋은 솔루션입니다.
tmux가 필요하지 않은 bash 네이티브/터미널 네이티브 솔루션이 있다면 이에 대해 듣고 싶습니다.
답변1
문제는 다음과 같습니다.
터미널은 "입력"을 통해 커서 위치를 보고합니다...
터미널에서 읽는 모든 프로그램의 경우 사용자가 입력하는 것과 터미널이 "입력하는" 것 사이에는 차이가 없습니다.
이런 경우에는 별도의 채널을 제공하는 터미널을 사용하세요.나는 사용한다 tmux
(다른 이유들). tmux
다음 작품 에서 :
_fetch_cursor_position() {
local pos
pos="$(tmux display-message -p -F '#{cursor_x} #{cursor_y}')"
_cursor_row="${pos#* }"
_cursor_col="${pos% *}"
((_cursor_row++))
((_cursor_col++))
}
요점은 이 함수가 표준 입력에서 읽지 않는다는 것입니다.별말씀을요.
tmux
시작부터 행/열을 계산합니다 0
. 이것들은 현재 코드의 나머지 부분과 호환되는 숫자를 만들기 위해 내 코드에 있습니다 ++
(분명히 처음부터 계산이 예상됩니다 1
). 처음부터 작성한다면 를 사용하지 않을 것입니다 ++
. 그에 따라 테스트를 작성하겠습니다(예: in -gt 0
대신 ).-gt 1
precmd()
답변2
단순화를 위해 코드에서 배열을 제거하고 참조용 커서 위치를 추가했습니다.
_fetch_cursor_position() {
echo -en "\e[25;10H"
read -s -t 1 -d ' ' -p $'\e[6n ' >&2
if [[ "$REPLY" ]]; then
result=$(echo $REPLY | sed 's/^[/\\e/g')
cat <<< "Cursor position in the terminal:
$result"
fi
}
_fetch_cursor_position
출력은 다음과 같습니다
Cursor position in the terminal:
\e[25;10R
echo
커서를 위치시키는 명령문과 관련이 있습니다.
^[
명령 에는 하드(실제) Esc 문자 가 있습니다 sed
. 이는 소프트 Esc(즉, 텍스트 표현)가 아닙니다. 실제 Esc를 입력하려면,
- 존재하다치수/윔, Ctrl+ 입력 V,Esc
- 존재하다이맥스, Ctrl+ Q,를 입력합니다.Esc