How we can code Ctrl+D in a shell script? - shell

I need to code Ctrl+D in a shell script in order to exit and display the result of a code.
The goal it's instead of "bye" in my code I tried to find a solution on google and in other website to replace "bye" by a Ctrl+D code.
Can you help me please?
This is my code :
touch file.txt
while :
do
shopt -s nocasematch
read INPUT_STRING
case $INPUT_STRING in
bob)
echo "boy">>file.txt
;;
alicia)
echo "girl">>file.txt
;;
cookie)
echo "dog">>file.txt
;;
bye)
cat file.txt
break
echo " "
;;
*)
echo "unknown">>file.txt
;;
esac
done

CTRL+D is not a sequence which is sent to input but closes the input so after CTRL+D read will exit with a non null exit code and the variable INPUT_STRING will be empty. in your script read exit code is not checked. Depending on what you need, you can either check if INPUT_STRING is empty or check read exit code.
while read INPUT_STRING; do
...
done
# put the code after
cat file.txt ...
or
while :; do
...
read INPUT_STRING || break
...
done
# put the code after
cat file.txt ...
or
case $INPUT_STRING in
...
'') # to match empty INPUT_STRING (could be because of CTRL+D)

How about checking the return status of read before going into your case?
E.g.:
if [ $? -eq 1 ] ;
...
fi
I don't think you can change the behaviour of read to trap the ctrl-d in the result variable.

Caveat: This answer literally does exactly what is requested. However, keyboard typed terminal input must quote the ctrl-D and send it ( ctrl+v   ctrl+d   Enter ) so the script can "see" it since bash keyboard processing will "trap & grab" and process the ctrl-D without propagation otherwise.
Consequently bash preprocesses, with some predefined function, every keyboard control code:
( ^# ^A ... ^Z ^[ ^\ ^] ^^ ^_ ^? where ^ means hold ctrl then type the character)
so that the ^V predefined function "quotes" and thus escapes other ctrl'dkey functionality, so the key character code can be propagated.
aside curio:
ctrl# aka ctrlshift2 over the decades usefully creates a null character code
ctrl[ aka Esc also useful when Esc is missing or broken
Cut to the chase:
echo ^v^d>ctrl-D.txt
gedit your-script.sh ctrl-D.txt
Highlight the ctrl-D character code (it is the only) character in ctrl-D.txt and copy and paste it to replace the bye in your-script.sh.
Note bene: the embedded code is exactly 0x04 preceded by white space and postceded by a ).
The script will now terminate on receiving a ctrl-D, but here's the rub. If bash is the shell it will not receive it because bash will have already processed it. AFAIK there does not exist a unix / linux shell which does not behave like this so it is necessary to quote the ctrl-D so the shell does not grab it. (Ctrl-D was one of the early primitives "used to end text input or to exit a Unix shell".)
To quote the ctrl-D and pass it on through the shell to the script type ctrl+v   ctrl+d   Enter in bash. YMMV (your mileage may vary) and some other quoting method may be available in other shells.
The long answer:
To embed a control code in a shell script quote it using ctrl+v.
It is understood that the ^letter sequences in the coding examples are not to be typed literally with an ^, but as a ctrl and letter combination.
Type ctrl+v ctrl+d contiguously.
(for expediency hold the ctrl and type v then d)
Thus typing echo ctrl+vd
linuxuser#ubuntu:~$ echo ^v^d
get this:
____
|00|
|04|
but as a single "code revealed" character, not the 12 _'s |'s #'s used here to represent the result.
To embed a control code like ^D in a file such as bye use
echo ctrl+vd >bye
Thus:
linuxuser#ubuntu:~$ echo now you can see -^v^d- via cat -v >bye
linuxuser#ubuntu:~$ cat -v bye
now you can see -^D- via cat -v
reveals the ctrld character by the two characters, an ^ and a D.
For the curious:
Note that quoting the quote to quote a code has been useful.
Thus (hold) ctrl (type) vvvz (and then release ctrl) will create a ^V^Z sequence in bash.
A poignant point worth noting is that files may contain arbitrary bytes. Ergo control codes per se do not "mean anything". It is on exposure to interpretation, like bash scripts, that various sequences might have meaning as control codes. It is very useful in bash that ctrlv can "disable" bash control code interpretation by its quoting mechanism. Most particularly, quoting is useful within files themselves that are to be interpreted by bash as scripts.
Coda:
linuxuser#ubuntu:~$ echo -e "echo 'ctrl-D^v^j -^v^d-' >bye \ncat -v bye" >doit.sh
linuxuser#ubuntu:~$ ./doit.sh
prints
ctrl-D
-^D-
tested with
linuxuser#ubuntu:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 18.04.2 LTS
Release: 18.04
Codename: bionic

Related

How to fix cursor position in Linux terminal using ANSI escape code?

I need to make it so that when I press a key, the cursor remains in its place (column) and just updates the symbol (doesn't move to the next column automatically). Is it possible to do this using ANSI escape code? I want to move the cursor only when I need it (for example, via printf("\e[1C");)
The terminal is in raw mode.
Put the terminal in non echo mode. Hence, your program controls what you want to display back on the screen.
Here is an example in shell which sets the terminal in raw and non echo mode, displays all the input chars at the same place (moving the cursor 1 step backward with "ESC[1D"), move the cursor 1 step forward if it is a space (with "ESC[1C") and exit if it is "Q":
#!/bin/sh
CSI="\e["
trap restore EXIT
restore() {
stty sane
}
# Terminal in RAW mode + non echo
stty cooked -echo
while true
do
read -n1 k >/dev/null 2>&1
case $k in
"") # Empty char (content of IFS= space, \n and TAB by default)
# move the cursor forward
echo -ne ${CSI}1C;;
Q) # quit the program
echo
exit 0;;
*) # Any other char is written in place
echo -ne $k${CSI}1D;;
esac
done
Doing the same in C is straightforward. Don't forget to call fflush(stdout) after the calls to printf() if they don't display \n

In bash print to line above terminal output

EDIT: Corrected process/thread terminology
My shell script has a foreground process that reads user input and a background process that prints messages. I would like to print these messages on the line above the input prompt rather than interrupting the input. Here's a canned example:
sleep 5 && echo -e "\nINFO: Helpful Status Update!" &
echo -n "> "
read input
When I execute it and type "input" a bunch of times, I get something like this:
> input input input inp
INFO: Helpful Status Update!
ut input
But I would like to see something like this:
INFO: Helpful Status Update!
> input input input input input
The solution need not be portable (I'm using bash on linux), though I would like to avoid ncurses if possible.
EDIT: According to #Nick, previous lines are inaccessible for historical reasons. However, my situation only requires modifying the current line. Here's a proof of concept:
# Make named pipe
mkfifo pipe
# Spawn background process
while true; do
sleep 2
echo -en "\033[1K\rINFO: Helpful Status Update!\n> `cat pipe`"
done &
# Start foreground user input
echo -n "> "
pid=-1
collected=""
IFS=""
while true; do
read -n 1 c
collected="$collected$c"
# Named pipes block writes, so must do background process
echo -n "$collected" >> pipe &
# Kill last loop's (potentially) still-blocking pipe write
if kill -0 $pid &> /dev/null; then
kill $pid &> /dev/null
fi
pid=$!
done
This produces mostly the correct behavior, but lacks CLI niceties like backspace and arrow navigation. These could be hacked in, but I'm still having trouble believing that a standard approach hasn't already been developed.
The original ANSI codes still work in bash terminal on Linux (and MacOS), so you can use \033[F where \033 is the ESCape character. You can generate this in bash terminal by control-V followed by the ESCape character. You should see ^[ appear. Then type [F. If you test the following script:
echo "original line 1"
echo "^[[Fupdated line 1"
echo "line 2"
echo "line 3"
You should see output:
updated line 1
line 2
line 3
EDIT:
I forgot to add that using this in your script will cause the cursor to return to the beginning of the line, so further input will overwrite what you have typed already. You could use control-R on the keyboard to cause bash to re-type the current line and return the cursor to the end of the line.

Shell script echo outputting arguments

I'm currently trying to build a shell script that sends broadcast UDP packets. My problem is that my echo is outputting the arguments instead, and I have no ideia why. Here's my script:
#!/bin/bash
# Script
var1="\xdd\x02\x00\x13\x00\x00\x00\x10\x46\x44\x30\x30\x37\x33\x45\x31\x39\x39\x45\x43\x31\x42\x39\x34\x00"
var2="\xdd\x00\x0a\x\x00\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x02"
echo -ne $var1 | socat - UDP4-DATAGRAM:255.255.255.255:5050,broadcast
echo -ne $var2 | socat - UDP4-DATAGRAM:255.255.255.255:5050,broadcast
Using wireshark I can see the script is printing -ne as characters and also is not converting each \xHH to the correspondant ASCII character.
Thanks!
I figured my problem out. It turns out I was runninc my script with sh ./script.sh instead of bash ./script.sh
echo implementations are hopelessly inconsistent about whether they take command options (like -ne) or simply treat them as part of the string to print, and/or whether they interpret escape sequences in the strings to print. It sounds like you're seeing a difference between bash's builtin version of echo vs. (I'm guessing) the version in /bin/echo. I've also seen it vary even between different versions of bash.
If you want consistent behavior for anything nontrivial, use printf instead of echo. It's slightly more complicated to use it correctly, but IMO worth it because your scripts won't randomly break because echo changed for whatever reason. The tricky thing about printf is that the first argument is special -- it's a format string in which all escape sequences are interpreted, and any % sequences tell it how to add in the rest of the arguments. Also, it doesn't add a linefeed at the end unless you specifically tell it to. In this case, you can just give it the hex codes in the format string:
printf "$var1" | socat - UDP4-DATAGRAM:255.255.255.255:5050,broadcast
printf "$var2" | socat - UDP4-DATAGRAM:255.255.255.255:5050,broadcast

Is it possible to make changes to a line written to STDOUT in shell?

Is it possible to make changes to a line written to STDOUT in shell, similar to the way many programs such as scp do?
The point would be to allow me to essentially have a ticker, or a monitor of some sort, without it scrolling all over the screen.
You can manipulate the terminal with control characters and ANSI escape codes. For example \b returns the cursor one position back, and \r returns it to the beginning of the line. This can be used to make a simple ticker:
for i in $(seq 10)
do
echo -en "Progress... $i\r" # -e is needed to interpret escape codes
sleep 1
done
echo -e "\nDone."
With ANSI escape codes you can do even more, like clear part of the screen, jump to any position you want, and change the output color.
You can overwrite the last printed line by printing the \r character.
For instance this:
for i in `seq 1 10`; do
echo -n $i;
sleep 1;
echo -n -e "\r" ;
done
Will print 1 then update it with 2 and so on until 10.
You can do modify the output of stdout using another program in a pipeline. When you run the program you use | to pipe the input into the next program. The next program can do whatever it wants with the output. A general purpose program for modifying the output of a program is sed, or you could write something yourself that modifies the data from the previous program.
A shell program would be something like:
while read line; do
# do something with $line and output the results
done
so you can just:
the_original_program | the_above_program

How to capture the title of a terminal window in bash using ANSI escape sequences?

I am using the bash command line in OSX. I know that the ANSI escape sequence \033[21t will retrieve the title of the current terminal window. So, for example:
$ echo -ne "\033[21t"
...sandbox...
$ # Where "sandbox" is the title of the current terminal window
$ # and the ... are some extra control characters
What I'd like to do is capture this information programmatically in a script, but I can't figure out how to do it. What the script captures just the raw ANSI escape sequence. So, for further example, this little Ruby script:
cmd = 'echo -ne "\033[21t"'
puts "Output from echo (directly to terminal):"
system(cmd)
terminal_name=`#{cmd}`
puts "\nOutput from echo to variable:"
puts terminal_name.inspect
Produces the following output:
Output from echo (directly to terminal):
^[]lsandbox^[\
Output from echo to variable:
"\e[21t"
I'd like the information in the second case to match the information shown on the terminal, but instead all I get is the raw command sequence. (I've tried using system() and capturing the output to a file -- that doesn't work, either.) Does anyone know a way to get this to work?
As detailed here you have to use dirty tricks to get that to work.
Here is a modified script:
#!/bin/bash
# based on a script from http://invisible-island.net/xterm/xterm.faq.html
exec < /dev/tty
oldstty=$(stty -g)
stty raw -echo min 0
# on my system, the following line can be replaced by the line below it
echo -en "\033[21t" > /dev/tty
read -r x
stty $oldstty
echo $x

Resources