3

I'm trying to write a simple script that read from standard input, using ; character as delimiter to terminate the input line and that allows user to edit line.

Here is my test script:

#!/bin/bash

while true; do

  read -e -d ";" -t 180 -p "><> " srcCommand

  if [ $? != 0 ]; then
    echo "end;"
    echo ""
    exit 0
  fi
  case "$srcCommand" in
    startApp)
       echo "startApp command";;
    stopApp)
       echo "stopApp command";;
    end)
       echo ""
       exit 0
       ;;
    *)
       echo "unknown command";;
  esac
done

This works but doesn't print the delimiter ';' char:

# bash test.sh
><> startApp
startApp command
><> stopApp
stopApp command
><> end

If I remove -e option it prints out ; but user can't correct his mistake using backspace character and echoed strings are just right after the delimiter:

# bash test.sh
><> startApp;startApp command
><> stopApp;stopApp command
><> end;

How can I print out the delimiter character and allow user to edit line while reading standard input?

This is the expected behavior:

# bash test.sh
><> startApp;
startApp command
><> stopApp;
stopApp command
><> end;

Thanks

2
  • I think -i option would work for you.. see stackoverflow.com/questions/2642585/…
    – Sundeep
    Oct 26, 2016 at 14:35
  • @Sundeep, thanks, but this is not useful in this case, if I add -i ";" together with -e option, read command puts text into the editing buffer before editing begins, and I get: ><> ;startApp end test.sh script doesn't recognize any command.
    – Lety
    Oct 26, 2016 at 15:00

1 Answer 1

4

I'd use zsh where the line editor has many more capabilities and is a lot more customizable:

#! /bin/zsh -
insert-and-accept() {
  zle self-insert
  # RBUFFER= # to discard everything on the right
  zle accept-line
}
zle -N insert-and-accept
bindkey ";" insert-and-accept
bindkey "^M" self-insert
vared -p "><> " -c srcCommand

With bash-4.3 or above, you can do something similar with a hack like:

# bind ; to ^Z^C (^Z, ^C otherwide bypass the key binding when entered
# on the keyboard). Redirect stderr to /dev/null to discard the
# useless warning
bind '";":"\32\3"' 2> /dev/null

# new widget that inserts ";" at the end of the buffer.
# If we did bind '";":";\3"', readline would loop indefinitely
add_semicolon() {
  READLINE_LINE+=";"
  ((READLINE_POINT++))
}
# which we bind to ^Z
bind -x '"\32":add_semicolon' 2> /dev/null

# read until the ^C
read -e -d $'\3' -t 180 -p '><> ' srcCommand

Note that in that version, the ; is always inserted at the end of the input buffer, not at the current cursor position. Change the add_semicolon to:

add_semicolon() {
  READLINE_LINE="${READLINE_LINE:0:READLINE_POINT++};"
}

If you want it inserted at the cursor and everything on the right discarded. Or:

add_semicolon() {
  READLINE_LINE="${READLINE_LINE:0:READLINE_POINT};${READLINE_LINE:READLINE_POINT}"
  READLINE_POINT=${#READLINE_LINE}
}

if you want to insert it at the cursor but want to preserve what's on the right like in the zsh approach.

If you don't want the ; in $srcCommand, you can always strip it afterwards with srcCommand="${srcComman//;}" for instance, but you'd need to insert it in the widget for it to be displayed by zle/readline.

2
  • I'll use add_semicolon that inserts at the cursor and discards everything on the right. thanks a lot this was exactly what I was looking for and even more!
    – Lety
    Oct 26, 2016 at 15:26
  • Do you know how I would use a list of keys or a variable in place of "\32" in the bind -x command?
    – dlsso
    May 19, 2017 at 23:20

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .