stdin에서 읽지 않고 tty에서 커서 위치 가져오기(리디렉션에 대한 도움 필요)

stdin에서 읽지 않고 tty에서 커서 위치 가져오기(리디렉션에 대한 도움 필요)

나는 썼다아름다운 사용자 정의 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 게시물을 참조하세요).

  1. 터미널이 커서 위치를 보고하도록 요구하는 이스케이프 시퀀스를 ^[[6n터미널에 인쇄합니다. 이는 read(ab)using 플래그에 의해 -p prompt인쇄 됩니다(참조 help read).
  2. 터미널은 ^[[y;xR터미널에 "입력"하여 커서 위치를 보고합니다. 여기서:
    ^[[: Esc ^[문자 뒤에 텍스트 가 [ y: 위에서부터 줄 번호(9보다 클 수 있음)
    x: 위에서부터 왼쪽 아래까지의 열 번호, 1부터 시작 (>9일 수 있음)
    R: 리터럴 문자R
  3. 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

내 마음속에 있는 과정은 이렇다.

  1. 0fd를 새 파일 설명자에 백업합니다 (stdout/ 1도?)
  2. 동일한 터미널에서 사용할 수 있도록 터미널( /dev/tty/ ?) 에 새 stdin/stout를 엽니다 (물론 커서를 이동하지 않고 위의 모든 작업).$(tty)
  3. 터미널에 위치를 요청하고 응답을 구문 분석합니다.
  4. 원본 복원

다른 FD로의 출력 위치

터미널이 0stdin/ 대신 다른 FD 에 좌표 응답을 쓰고 read거기에서 읽도록 하세요.

이미 존재하는 표준 입력 데이터에 접근할 수 없는 서브쉘입니다.

_fetch_cursor_positionstdin/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 위상 재지정 printfread

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 사용

Kamil Machorovsky의 답변 보기

나는 항상 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 1precmd()

답변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

관련 정보