Christian Weisgerber <
na...@mips.inka.de> wrote:
> […]
>> Heute stöbere ich noch einmal durch die Man-Page und stoße
>> auf die Shell-Variablen „READLINE_ARGUMENT“,
>> „READLINE_LINE“, „READLINE_MARK“ und „READLINE_POINT“ und
>> deren Verweise auf „bind -x“:
>> Das hört sich ja an, als könnte man zap-to-char damit prin-
>> zipiell in Bash implementieren.
> Ein Problem ist, dass das Zeichen, bis zu dem gelöscht werden soll,
> irgendwie eingelesen werden muss. Ich habe mal schnell sowas
> gebastelt:
> zap_to_char()
> {
> local head tail char
> head=${READLINE_LINE:0:$READLINE_POINT}
> tail=${READLINE_LINE:$READLINE_POINT}
> read -rN1 char
> tail=${tail#*${char}}
> READLINE_LINE=$head$tail
> }
> bind -x '"\ez":zap_to_char'
> Leider wird vor der Ausführung der Funktion die Zeile im Terminal
> gelöscht und erst anschließend wieder angezeigt, das read also auf
> einer leeren Zeile ausgeführt. Außerdem ist der gelöschte Teil dann
> natürlich nicht im Kill-Buffer wie bei M-z in Emacs.
> Ich denke, am sinnvollsten wäre es, zap-to-char direkt in libreadline
> zu implementieren. Wahrscheinlich kann man den Code im Wesentlichen
> von anderen Funktionen kopieren.
Eine Implementation in libreadline wäre sicherlich optimal.
Der Vorteil von „bind -x“ ist natürlich, dass die Funktiona-
lität /jetzt/ verfügbar wird und nicht in ein paar Jah-
ren :-). Außerdem fand ich es interessant, ein Beispiel für
„bind -x“ zu entwickeln, das einen realen Nutzen hat.
Ich habe Deine und Stefans Inspirationen (danke!) daher wei-
terverarbeitet. Ich wollte Emacs’ Verhalten hinsichtlich des
universalen Argumentes möglichst identisch nachbauen, und,
auch wenn libreadline leider keinen transient-mark-mode
kennt und ich daher praktisch nie die Mark auf der Befehls-
zeile benutze, auch deren Positionierung beachten:
| # zap_to_char ARG CHAR.
| function zap_to_char {
| # Default to deleting to the first occurence of CHAR.
| local arg="${READLINE_ARGUMENT:-1}"
| # Read character to zap to.
| local ch
| read -d '' -rsn 1 ch
| local line_before_point="${READLINE_LINE:0:${READLINE_POINT}}"
| local line_after_point="${READLINE_LINE:${READLINE_POINT}}"
| while [ "$arg" -gt 0 ]; do
| local new_line_after_point="${line_after_point#*${ch}}"
| # If CHAR cannot be found ARG times in the current
| # line after point, fail.
| if [ "$line_after_point" = "$new_line_after_point" ]; then
| return
| fi
| line_after_point="$new_line_after_point"
| arg=$(("$arg" - 1))
| done
| # Reposition mark.
| # If the mark was not after the point, do nothing.
| if [ "$READLINE_MARK" -gt "$READLINE_POINT" ]; then
| # Otherwise, the mark gets moved left as many
| # characters as the line after the point shrunk by,
| # but no further left than the point.
| READLINE_MARK=$((READLINE_MARK - (${#READLINE_LINE} - READLINE_POINT - ${#line_after_point})))
| if [ "$READLINE_MARK" -lt "$READLINE_POINT" ]; then
| READLINE_MARK="${READLINE_POINT}"
| fi
| fi
| # Set new line.
| READLINE_LINE="${line_before_point}${line_after_point}"
| }
| bind -x '"\ez":zap_to_char'
| # Test one zap_to_char call.
| # test_zap_to_char-1 INPUT ARG CHAR EXPOUT.
| # The characters "#" and "!" are used to denote the position
| # of the mark and the point, respectively. If both the mark
| # and the point are at the same position, the order is "#!".
| function test_zap_to_char-1 {
| local READLINE_LINE="$(printf %s "$1" | sed -e 's/[#!]//g;')"
| local READLINE_POINT="$(printf %s "$1" | sed -e 's/^\([^#!]*\)\(#\([^#!]*\)\)\?!.*$/\1\3/;' | wc -c)"
| local READLINE_MARK="$(printf %s "$1" | sed -e 's/^\([^#!]*\)\(!\([^#!]*\)\)\?#.*$/\1\3/;' | wc -c)"
| local READLINE_ARGUMENT="$2"
| if [ "$READLINE_ARGUMENT" = 'x' ]; then
| unset READLINE_ARGUMENT
| fi
| zap_to_char < <(echo -nE "$3")
| local actual_output="${READLINE_LINE:0:${READLINE_POINT}}!${READLINE_LINE:${READLINE_POINT}}"
| if [ $READLINE_MARK -le $READLINE_POINT ]; then
| actual_output="${actual_output:0:$READLINE_MARK}#${actual_output:$READLINE_MARK}"
| else
| actual_output="${actual_output:0:$READLINE_MARK + 1}#${actual_output:$READLINE_MARK + 1}"
| fi
| if [ "$actual_output" = "$4" ]; then
| echo ok
| else
| echo "not ok: Expected \"$4\", got \"$actual_output\"."
| fi
| }
| # Test suite for zap_to_char.
| function test_zap_to_char {
| # Basic functionality.
| test_zap_to_char-1 'ABC#!DEFGHIJKLMNOPQRSTUVWXYZ' x 'J' 'ABC#!KLMNOPQRSTUVWXYZ'
| test_zap_to_char-1 'ABC#!DEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ' 2 'J' 'ABC#!KLMNOPQRSTUVWXYZ'
| # Character cannot be found after point.
| test_zap_to_char-1 'ABC#!DEFGHIJKLMNOPQRSTUVWXYZ' x 'j' 'ABC#!DEFGHIJKLMNOPQRSTUVWXYZ'
| # Character cannot be found not enough times after point.
| test_zap_to_char-1 'ABC#!DEFGHIJKLMNOPQRSTUVWXYZ' 2 'J' 'ABC#!DEFGHIJKLMNOPQRSTUVWXYZ'
| # Preserve mark.
| test_zap_to_char-1 'ABC#DE!FGHIJKLMNOPQRSTUVWXYZ' x 'J' 'ABC#DE!KLMNOPQRSTUVWXYZ'
| test_zap_to_char-1 'ABCDE!FG#HIJKLMNOPQRSTUVWXYZ' x 'J' 'ABCDE#!KLMNOPQRSTUVWXYZ'
| test_zap_to_char-1 'ABCDE!FGHIJKLM#NOPQRSTUVWXYZ' x 'J' 'ABCDE!KLM#NOPQRSTUVWXYZ'
| }
| test_zap_to_char
Was ich in jedem Fall mitgenommen habe: Man kann in der Bash
Tasten nicht nur auf libreadline-Primitive konfigurieren
oder kontextlose Eingabekombinationen, die hoffentlich tun,
was sie sollen, sondern hat die Möglichkeit, die Befehlszei-
le komplett zu manipulieren mit allen Mitteln, die in Shell-
Funktionen zu Verfügung stehen.
Besten Dank noch einmal,
Tim