Readline의 킬링을 확인하려면 매우 해킹된 Bash 스크립트에 대한 도움이 필요합니다.

Readline의 킬링을 확인하려면 매우 해킹된 Bash 스크립트에 대한 도움이 필요합니다.

나는 Bash에서 킬링 서클을 시각화하는 장치를 설계했습니다.

작동 방식은 다음과 같습니다.

$ killring
Killring:
1 Line one in killring
2 Line two in killring
3 Line three in killring

그러나 몇 가지 문제도 있습니다.

  1. killring이 비어 있으면 사용자는 계속하려면 Enter를 눌러야 합니다. 이는 내부 함수 이름으로 다시 캐스팅되었으며 이제 오류 메시지로도 사용됩니다.

    $ killring
    __Killring_is_empty__Press_enter_to_continue <<'EOF'
    > EOF
    
  2. 함수가 성공하면 프롬프트 뒤에 관련 없는 문자가 표시됩니다.

    $ killring
    Killring:
    1 Line one in killring
    2 Line two in killring
    3 Line three in killring
    ^[[0n$
    
  3. 100줄의 회전하는 킬 루프를 인쇄하는 중간 기능을 숨기기 위해 화면을 지워야 했습니다. 하지만 그건 내가 감당할 수 있는 문제다.

  4. /dev/null리디렉션을 제거하면 관련 없는 메시지가 표시됩니다.

    $ killring
    Killring:
    1 Line one in killring
    2 Line two in killring
    3 Line three in killring
    gstty: 'standard input': Inappropriate ioctl for device
    gstty: 'standard input': Inappropriate ioctl for device
    ^[[0ngstty: invalid argument ‘’
    Try 'gstty --help' for more information.
    

소스 코드는 다음과 같습니다.

# from https://unix.stackexchange.com/a/217390/569343
# write provided text to the terminal input
# (does not work in subshells)
# - There are some weird errors when I remove the 2>/dev/nulls, but I don't
#   even know how to begin to fix them.
function write_to_input () {
  bind '"\e[0n": "'"$*"'"'

  saved_settings=$(stty -g 2>/dev/null)
  stty -echo -icanon min 1 time 0 2>/dev/null
  printf '\e[5n'
  until read -r -t0; do
    sleep 0.02
  done
  stty "$saved_settings" 2>/dev/null
}

# Killring internal function
# - this function performs the actual job of calculating and printing the
#   killring
# - the strange name is because, due to the ungodly hack required for this
#   thing to work, there is an edge case when the kill ring is empty, in which
#   this function will be typed into the Readline input line but the user still
#   needs to press enter. Therefore the function itself acts as the
#   user-friendly message.
# - It works like this:
#   - It guesses the content of the keyring after 10 rotations, and prints it.
#   - It calculates how many times it must rotate to return to the original
#     position, and performs the rotation by editing the Readline input line.
#   - Then it preses delete 500 times, in the hope of returning the input line
#     in pristine condition.
function __Killring_is_empty__Press_enter_to_continue () {
  local array;
  readarray -t array

  # if array is empty, killring is empty
  if [[ ${#array[@]} == 0 ]]; then
    return
  fi

  # the last yanked thing
  local current_position=$((${#array[@]} - 1))

  # find index of element that matches the first, or -1 if none
  # Note: this script assumns that there are no repeated elements in the
  #       killring. If that's not the case, the ring will rotate from the
  #       original position.
  #       A better algorithm to detect repeated elements could help, but I
  #       can't be arsed.
  local index_of_repeat=-1
  for (( i = 1; i < ${#array[@]}; i++ )); do
    if [[ "${array[$i]}" == "${array[0]}" ]]; then
      index_of_repeat=$i
      break
    fi
  done

  # reduce array to the actual killring
  if [[ $index_of_repeat != -1 ]]; then
    array=("${array[@]:0:$index_of_repeat}")
  fi

  local killring_size="${#array[@]}"
  
  # print killring
  local green='\x1b[32m'
  local reset='\x1b[0m'
  clear
  echo "${green}"Killring:"${reset}"
  for (( i = 0; i < ${#array[@]}; i++ )); do
    echo "${green}$i${reset} ${array[$i]}"
  done

  local needed_pops=0
  local killring_position=$((current_position % killring_size))
  if [[ $killring_position != 0 ]]; then
    needed_pops=$((killring_size - killring_position))
  fi

  # doing the pops
  local command='\C-y'
  for (( i = 0; i < needed_pops; i++ )); do
    command="$command\ey"
  done

  # append 500 backspaces to clear the input (\C-?)
  # Forgive me, Lord.
  for (( i = 0; i < 500; i++ )); do
    command="$command\C-?"
  done
  write_to_input "$command"
  set -o history
}

# show the current killring
function killring () {
  set +o history

  # a killring has a maximum size of 10 in bash, so this should be enough to
  # figure out the contents
  local size=10
  
  # compose command to show killring.
  # - the command consists of pressing \C-y to yank 100 times, hoping to cover
  #   the entire killring.
  # - for some reason, \C-y stops the command generation if there's nothing in
  #   the killring, so we leave the command in a functional state in that
  #   situation, so that the user can press enter to proceed.
  local command="__Killring_is_empty__Press_enter_to_continue <<'EOF'\nEOF"
  command="$command\C-y\C-a\C-d\C-d\C-d\n"
  for (( i = 1; i < size; i++ )); do
    command="$command\C-y\ey\n"
  done
  command="${command}EOF\n"
  write_to_input "$command"
}

물론, 혹시 킬링 서클을 보는 다른 방법을 알고 계시다면 알려주세요.

관련 정보