zsh에서 Home, End 및 Del은 스크립트 내부와 외부에서 다르게 동작합니다.

zsh에서 Home, End 및 Del은 스크립트 내부와 외부에서 다르게 동작합니다.

많은 좌절 끝에 처음에는 메인 셸이 아닌 스크립트에 영향을 미치 $EDITOR도록 설정되었으며 이 초기 상태를 되돌릴 수 있는 것이 아무것도 없는 것 같았다는 것을 알게 되었습니다 . vim환경 변수를 설정 해제하고 빈 문자열로 설정하면 사용법이 bindkey -e스크립트에 전파되지 않습니다. bindkey -v기본 셸을 사용하여 모드를 전환하고 다시 전환 하면 모든 키가 두 모드에서 예상대로 작동합니다.

스크립트 내에서 vi 편집은 기본 셸과 동일하게 잘 작동하지만 실행 후 bindkey -e입력이 이상하게 작동합니다. 후속 키 입력 및 인쇄 에만 방해가 Home됩니다 .EndDel~

키 누르기가 보내는 내용을 테스트하고 각 키 누르기 앞에 Ctrl+ 를 추가하면 기본 셸로 들어가지만 V스크립트 내로 들어갑니다. 그래서 두 개는 다르고 다르게 행동하고, 하나는 동일하지만 다르게 행동합니다.[OH^[OF^[[3~[[H^[[F^[[3~

물론 스크립트 이식성을 위해서만 키 바인딩을 정의할 필요는 없습니다. 왜 깨지나요?

Zsh는 v5.9이며 스크립트는 다음과 같습니다.

#!/usr/bin/zsh
bindkey -e
vared -p 'x: ' -c x

나는 미리 입력된 제안으로 문자열 편집에 대한 힌트를 제공하고 싶고 사람들은 키가 텍스트 입력에서 어떻게든 작동하기를 원합니다. 다른 Emacs 시퀀스도 작동하지만 기술적인 지식이 없는 사용자에게는 가르치지 않는 것이 가장 좋습니다.

답변1

Home, End, Insert, , Delete, 는 기본적으로 zsh 키맵(emacs, vi-insert, vi-command)이 있는 키보드와 Binding 을 눌렀을 때 고유한 문자 또는 문자 시퀀스를 전송하는 터미널에 대한 키맵이 아닙니다 PageUp. PageDown.., 전체 목록은 참조 bindkey -l또는 $keymaps배열).

또는 , 위의 기능 키 또는 기능 키 또는 화살표 키 또는 와 결합하면 Tab바인딩이 전혀 없습니다(해당 키에 대한 고유 시퀀스를 전송하는 터미널의 경우에도 마찬가지).BackspaceEscapeShiftControlAlt

UpDownLeftRight화살표 키( )는 대부분의 터미널이 이러한 키에 동일한 이스케이프 시퀀스를 보내기 때문에 기본적으로 바인딩되어 있습니다. 이는 터미널 및/또는 터미널에 있는지 여부에 따라 거의 항상 ^[[A... ^[[D또는 ^[OA... 입니다.^[OD키보드 전송 모드(참조 smkx) terminfo(5)그렇지 않습니다.

옵션(시스템 또는 사용자 초기화 파일 건너뛰기(zshenv 제외))을 사용하여 실행하고 다음을 실행하여 다양한 키맵 zsh의 기본 키 바인딩을 볼 수 있습니다.zsh-f

for m ($keymaps) bindkey -M $m | grep -H --label=$m .

수동또한 키맵의 어떤 키 에 대해 어떤 emacs위젯이 발견되었는지도 표시됩니다 .vicmdviins

위 루프를 에 파이프하면 다음 grep '\^\[[[O][A-D]'값에 관계없이 해당 내용이 표시됩니다 $TERM.

% (for m ($keymaps) bindkey -M $m | grep -H --label=$m .) | grep '\^\[[[O][A-D]'
visual:"^[OA" up-line
visual:"^[OB" down-line
visual:"^[[A" up-line
visual:"^[[B" down-line
viopp:"^[OA" up-line
viopp:"^[OB" down-line
viopp:"^[[A" up-line
viopp:"^[[B" down-line
vicmd:"^[OA" up-line-or-history
vicmd:"^[OB" down-line-or-history
vicmd:"^[OC" vi-forward-char
vicmd:"^[OD" vi-backward-char
vicmd:"^[[A" up-line-or-history
vicmd:"^[[B" down-line-or-history
vicmd:"^[[C" vi-forward-char
vicmd:"^[[D" vi-backward-char
main:"^[OA" up-line-or-history
main:"^[OB" down-line-or-history
main:"^[OC" forward-char
main:"^[OD" backward-char
main:"^[[A" up-line-or-history
main:"^[[B" down-line-or-history
main:"^[[C" forward-char
main:"^[[D" backward-char
viins:"^[OA" up-line-or-history
viins:"^[OB" down-line-or-history
viins:"^[OC" vi-forward-char
viins:"^[OD" vi-backward-char
viins:"^[[A" up-line-or-history
viins:"^[[B" down-line-or-history
viins:"^[[C" vi-forward-char
viins:"^[[D" vi-backward-char
emacs:"^[OA" up-line-or-history
emacs:"^[OB" down-line-or-history
emacs:"^[OC" forward-char
emacs:"^[OD" backward-char
emacs:"^[[A" up-line-or-history
emacs:"^[[B" down-line-or-history
emacs:"^[[C" forward-char
emacs:"^[[D" backward-char

대부분의 키맵에서 이 4개의 화살표 키에 대한 두 이스케이프 시퀀스는 주어진 상황에서 일반적으로 수행할 것으로 예상되는 작업과 연결되어 있습니다.

이러한 위젯은 또한 일반적으로 사용되는 emacs/vi 키(예: ^Bemacs 모드의 ^F, , , ^P또는 vi-cmd 모드의 , , ,)에 바인딩됩니다.^Nhjkl

물론 모든 터미널이 보내는 잘 알려진 단일 제어 문자인 , 및 에 대한 바인딩도 찾을 수 있습니다 Esc( Tab비록 BS를 보내는 터미널이 있고 DEL을 보내는 터미널이 있지만).BackspaceEnterBackspace

그러나 다른 기능 키에 대한 정보는 찾을 수 없습니다.

예를 들어, 터미널을 눌렀을 때 터미널이 보내는 내용에 대한 요약을 가져오는 방법이 있는데, 이는 terminfo 데이터베이스에 알려져 있습니다 End.

$ (typeset -A count; for TERM (/usr/share/terminfo/*/*(.:t)) (( count[\$terminfo[kend]]++ )); typeset -p1 count)
typeset -A count=(
  [$'\M-\C-@O']=8
  ['']=1341
  [$'\M-\C-?\M-(']=6
  [$'\C-Ak\C-M']=1
  [$'\C-SI']=3
  [$'\C-[)4\C-M']=4
  [$'\C-[0']=8
  [$'\C-[F']=1
  [$'\C-[K']=4
  [$'\C-[OF']=99
  [$'\C-[T']=13
  [$'\C-[Y']=1
  [$'\C-[[146q']=23
  [$'\C-[[1~']=11
  [$'\C-[[220z']=21
  [$'\C-[[24;1H']=3
  [$'\C-[[4~']=139
  [$'\C-[[5~']=9
  [$'\C-[[8~']=28
  [$'\C-[[F']=49
  [$'\C-[[K']=1
  [$'\C-[[OF']=1
  [$'\C-[[U']=15
  [$'\C-[[Y']=16
  [$'\C-[[d']=1
  [$'\C-[_1\C-[\\']=1
  [$'\C-[k']=3
  [$'\C-[z']=13
  ['- @']=1
  ['-45~']=1
  ['-4~']=5
  [1!]=1
)

대부분의 터미널에서는 그들이 보내는 내용이 무엇인지 알 수 없습니다. $'\C-[[4~'가장 일반적입니다.

Delete지금은 일반적으로 전송되지만 \e[3~때때로 DEL( ^?)(가장 일반적으로 백스페이스 키로 전송됨)이 전송되며 많은 사람들이 이를 터미널 에뮬레이터 설정에서 구성할 수 있습니다. 일부 터미널 에뮬레이터는 에뮬레이트할 키보드 유형을 알려주고 해당 기능 키에 대해 다른 시퀀스를 보냅니다. 예를 들어 설명서를 인용한 xterm을 참조하세요.

-kt 키보드 유형
이 옵션은 keyboardType리소스를 설정합니다. 가능한 값은 "unknown", "default", "legacy", "hp", "sco", "sun", "tcap" 및 "vt220"입니다.

이제 사용자는 어떤 유형의 키보드를 사용할 것인지, 어떤 터미널 에뮬레이터와 어떤 시스템에서 사용할 것인지 알 수 있습니다. 운영 체제 공급업체는 zsh보다 더 정확한 추측을 할 수도 있습니다(zsh는 30년 넘게 수천 개의 다양한 시스템에서 사용되었습니다).

예를 들어, x86 PC용 Debian GNU/Linux 배포판은 패키지에 포함된 수십 개의 터미널 에뮬레이터(대부분 유사함 xterm)에 합리적으로 충실한 terminfo 데이터베이스를 유지하려고 시도합니다. 일반적으로 전송되는 여러 기능 키를 알고 있습니다. PC 키보드에 있는 키보드는 사용자가 터미널 에뮬레이터의 기본 구성을 변경하지 않고 외부 운영 체제에서 원격으로 로그인하지 않는 한 사용할 수 있습니다.

/etc/zsh/zshrc따라서 데비안이 이것을 (대화식으로 호출되는 시스템 사용자 정의 파일 ) 에 추가한다는 것을 알게 될 것입니다 zsh:

# /etc/zsh/zshrc: system-wide .zshrc file for zsh(1).
#
# This file is sourced only for interactive shells. It
# should contain commands to set up aliases, functions,
# options, key bindings, etc.
#
# Global Order: zshenv, zprofile, zshrc, zlogin

READNULLCMD=${PAGER:-/usr/bin/pager}

# An array to note missing features to ease diagnosis in case of problems.
typeset -ga debian_missing_features

if [[ -z "${DEBIAN_PREVENT_KEYBOARD_CHANGES-}" ]] &&
   [[ "$TERM" != 'emacs' ]]
then

    typeset -A key
    key=(
        BackSpace  "${terminfo[kbs]}"
        Home       "${terminfo[khome]}"
        End        "${terminfo[kend]}"
        Insert     "${terminfo[kich1]}"
        Delete     "${terminfo[kdch1]}"
        Up         "${terminfo[kcuu1]}"
        Down       "${terminfo[kcud1]}"
        Left       "${terminfo[kcub1]}"
        Right      "${terminfo[kcuf1]}"
        PageUp     "${terminfo[kpp]}"
        PageDown   "${terminfo[knp]}"
    )

    function bind2maps () {
        local i sequence widget
        local -a maps

        while [[ "$1" != "--" ]]; do
            maps+=( "$1" )
            shift
        done
        shift

        sequence="${key[$1]}"
        widget="$2"

        [[ -z "$sequence" ]] && return 1

        for i in "${maps[@]}"; do
            bindkey -M "$i" "$sequence" "$widget"
        done
    }

    bind2maps emacs             -- BackSpace   backward-delete-char
    bind2maps       viins       -- BackSpace   vi-backward-delete-char
    bind2maps             vicmd -- BackSpace   vi-backward-char
    bind2maps emacs             -- Home        beginning-of-line
    bind2maps       viins vicmd -- Home        vi-beginning-of-line
    bind2maps emacs             -- End         end-of-line
    bind2maps       viins vicmd -- End         vi-end-of-line
    bind2maps emacs viins       -- Insert      overwrite-mode
    bind2maps             vicmd -- Insert      vi-insert
    bind2maps emacs             -- Delete      delete-char
    bind2maps       viins vicmd -- Delete      vi-delete-char
    bind2maps emacs viins vicmd -- Up          up-line-or-history
    bind2maps emacs viins vicmd -- Down        down-line-or-history
    bind2maps emacs             -- Left        backward-char
    bind2maps       viins vicmd -- Left        vi-backward-char
    bind2maps emacs             -- Right       forward-char
    bind2maps       viins vicmd -- Right       vi-forward-char

    # Make sure the terminal is in application mode, when zle is
    # active. Only then are the values from $terminfo valid.
    if (( ${+terminfo[smkx]} )) && (( ${+terminfo[rmkx]} )); then
        function zle-line-init () {
            emulate -L zsh
            printf '%s' ${terminfo[smkx]}
        }
        function zle-line-finish () {
            emulate -L zsh
            printf '%s' ${terminfo[rmkx]}
        }
        zle -N zle-line-init
        zle -N zle-line-finish
    else
        for i in {s,r}mkx; do
            (( ${+terminfo[$i]} )) || debian_missing_features+=($i)
        done
        unset i
    fi

    unfunction bind2maps

fi # [[ -z "$DEBIAN_PREVENT_KEYBOARD_CHANGES" ]] && [[ "$TERM" != 'emacs' ]]
[...]

위에서 볼 수 있듯이 키 이름을 해당 이스케이프 시퀀스에 매핑하는 연관 배열을 정의합니다. 이러한 이스케이프 시퀀스는 기본적으로 데비안과 함께 제공되는 terminfo 데이터베이스에서 검색됩니다(항목 목록은 기본적으로 제한되어 있지만).

그러나 terminfo 데이터베이스는 터미널에서 보낸 이스케이프 시퀀스만 제공하기 때문에키보드 전송 모드, zshrc가 ZLE에게 시작 시 이 모드로 들어가고 종료 시 그대로 두도록 지시하는 것을 볼 수 있습니다.

zshrc이는 대화형 zsh 셸(셸이 실행되는 위치)의 프롬프트(ZLE 내)와 프롬프트 없이 서로 다른 시퀀스가 ​​표시되는 이유를 설명합니다.

zsh의 스크립트 및 보다 일반적인 비대화형 호출은 zshrc를 읽지 않습니다. zshrc는 모든 사용자 정의, 별칭, 함수를 저장하는 곳이기 때문에 스크립트가 이를 읽으면 확실히 깨질 것입니다.

이제 ZLE vared도 사용되며, zshrc를 읽지 않는 스크립트에서 호출되면 이는 /etc/zsh/zshrc에서 운영 체제가 제공하는 바인딩을 가져오지 않음을 의미합니다.

따라서 이러한 바인딩이 마음에 들고 해당 스크립트 사용자가 동일한 운영 체제를 사용하는지 여부에 관계없이 해당 사용자가 사용할 수 있도록 하려면 이를 스크립트에 직접 포함해야 합니다.

또한 ZLE의 기본값은 emacsor로 시작하는지 여부에 따라 or 모드로 설정됩니다. 스크립트 사용자가 일관된 키 바인딩을 사용하도록 하려면 패턴을 강제로 적용해야 할 수도 있습니다. 기본적으로 emacs 또는 vi 모드가 선택되었는지( 또는 사용)에 따라 또는 의 별칭인 키맵 바인딩이 구성됩니다.vi$EDITOR$VISUALvi/vibindkeymainemacsviinsbindkey -ebindkey -v

데비안 외에도 다른 편집기의 동작을 모방하기 Shift위해 Alt// 와 결합하는 것을 포함하여 기능 키 바인딩에 대한 다른 많은 사용자 정의 제안을 찾을 수 있습니다 (해당 편집기에 고유한 시퀀스를 보내는 터미널에서).Ctrl

예를 들어 참조하십시오.zsh zle 시프트 선택스택 오버플로 정보oh-my-zsh키바인딩그들로부터 영감을 얻을 수 있습니다.


1 이와 같은 문제가 발생하지 않도록 csh 스크립트에 일반적 으로 읽기를 건너뛰는 #! /bin/csh -fshebang이 있는 방법을 확인하세요.-f~/.cshrc

관련 정보