6

I'm writting a bash wrapper to learn some scripting concepts. The idea is to write a script in bash and set it as a user's shell at login.

I made a while loop that reads and evals user's input, and then noticed that, whenever user typed CTRL + C, the script aborted so the user session ends.

To avoid this, I trapped SIGINT, doing nothing in the trap.

Now, the problem is that when you type CTRL + C at half of a command, it doesn't get cancelled as one would do on bash - it just ignores CTRL + C.

So, if I type ping stockoverf^Cping stackoverflow.com, I get ping stockoverfping stackoverflow.com instead of the ping stackoverflow.com that I wanted.

Is there any way to do that?

#!/bin/bash

# let's trap SIGINT (CTRL + C)
trap "" SIGINT

while true
do
    read -e -p "$USER - SHIELD: `pwd`> " command
    history -s $command
    eval $command
done
mgarciaisaia
  • 14,521
  • 8
  • 57
  • 81

2 Answers2

5

I know this is old as all heck, but I was struggling to do something like this and came up with this solution. Hopefully it helps someone else out!

#/usr/bin/env bash
# Works ok when it is invoked as a bash script, but not when sourced!
function reset_cursor(){
    echo
}
trap reset_cursor INT
while true; do
    command=$( if read -e -p "> " line ; then echo "$line"; else echo "quit"; fi )
    if [[ "$command" == "quit" ]] ; then
        exit
    else
        history -s $command
        eval "$command"
    fi
done
trap SIGINT

By throwing the read into a subshell, you ensure that it will get killed with a sigint signal. If you trap that sigint as it percolates up to the parent, you can ignore it there and move onto the next while loop. You don't have to have reset_cursor as its own function but I find it nice in case you want to do more complicated stuff.

I had to add the if statement in the subshell because otherwise it would ignore ctrl+d - but we want it to be able 'log us out' without forcing a user to type exit or quit manually.

CGanote
  • 149
  • 1
  • 6
  • This works nicely. I have implemented it as follows to obtain user input as a list of words: `trap echo INT; while true; do mapfile -t -d '' input < <(IFS=' ' read -r -e -p '> ' -a input; printf '%s\0' "${input[@]}"); [[ ${input[0]} == quit ]] && break; history -s "${input[*]}"; declare -p input; done; trap SIGINT` – sorpigal Apr 21 '21 at 11:34
2

You could use a tool like xdotool to send Ctrl-A (begin-of-line) Ctrl-K (delete-to-end-of-line) Return (to cleanup the line)

#!/bin/bash
trap "xdotool key Ctrl+A Ctrl+k Return" SIGINT;
unset command
while [ "$command" != "quit" ] ;do
    eval $command
    read -e -p "$USER - SHIELD: `pwd`> " command
  done
trap SIGINT

The please have a look a bash's manual page, searching for ``debug'' keyword...

man -Pless\ +/debug bash
F. Hauri - Give Up GitHub
  • 64,122
  • 17
  • 116
  • 137
  • xdotool would do the trick, as a 100% hack. You get a `^C^A^K` and an empty line that wouldn't show in pure bash (just `^C`), but it's better than nothing. Anyway, it would be awesome to not depend on `xdotool`, and to avoid those extra markers - say, make it just like bash works. Anyway, I didn't get the rtfm invitation... What should debug show me? – mgarciaisaia Oct 25 '12 at 15:25
  • Yes, this is a huge hack. But in state, this do the job. The right way to follow the original idea need to access file descriptors via ioctl. – F. Hauri - Give Up GitHub Oct 25 '12 at 18:42
  • @mgarciaisaia About what *debug* could do and how it could be used. have a look at [profiling bash in nanoseconds](http://stackoverflow.com/a/20855353/1765658) – F. Hauri - Give Up GitHub Mar 26 '14 at 12:20