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

  • I think -i option would work for you.. see stackoverflow.com/questions/2642585/… – Sundeep Oct 26 '16 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 '16 at 15:00
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.

  • 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 '16 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 '17 at 23:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.