나는 Bash에서 킬링 서클을 시각화하는 장치를 설계했습니다.
작동 방식은 다음과 같습니다.
$ killring
Killring:
1 Line one in killring
2 Line two in killring
3 Line three in killring
그러나 몇 가지 문제도 있습니다.
killring이 비어 있으면 사용자는 계속하려면 Enter를 눌러야 합니다. 이는 내부 함수 이름으로 다시 캐스팅되었으며 이제 오류 메시지로도 사용됩니다.
$ killring __Killring_is_empty__Press_enter_to_continue <<'EOF' > EOF
함수가 성공하면 프롬프트 뒤에 관련 없는 문자가 표시됩니다.
$ killring Killring: 1 Line one in killring 2 Line two in killring 3 Line three in killring ^[[0n$
100줄의 회전하는 킬 루프를 인쇄하는 중간 기능을 숨기기 위해 화면을 지워야 했습니다. 하지만 그건 내가 감당할 수 있는 문제다.
/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"
}
물론, 혹시 킬링 서클을 보는 다른 방법을 알고 계시다면 알려주세요.