The case is as follows:
1) I have a script somescript.sh.
2) At the end of the script, there is an eval statement which triggers the execution of a JAR.
3) When the JAR is executed, it will ask the user to provide an a/b/c option as a possible answer - important fact is that this questions come from the JAR application, so the console "logic" regarding the questions is written in Java and not placed in the script itself.
The problem: while executing the JAR program, the user can press the arrow keys and this will result in an ugly outcome such as ^[[A ^[[B ^[[C ^[[D.
This thread clarifies the ugly outcome: Why the terminal shows "^[[A" "^[[B" "^[[C" "^[[D" when pressing the arrow keys in Ubuntu?
Question: How is it possible to disable the arrow keys while the JAR is executed?
Now this is rather convoluted, but it`s still better than nothing.
The code below is a primitive BASH key filter to bypass cursor keys and halt any input on pressing Enter. Currently it lacks backspace support, but this is also doable if need be.
Requires BASH 4.2 or greater.
while true; do
read -s -N1 c1
read -s -N2 -t 0.001 c2
read -s -N1 -t 0.001 c3
case "$c1$c2$c3" in
$'\x0a' | $'') echo "";
break ;; # Enter
$'\x1b\x5b\x41') ;; # Up arrow
$'\x1b\x5b\x42') ;; # Down arrow
$'\x1b\x5b\x43') ;; # Right arrow
$'\x1b\x5b\x44') ;; # Left arrow
*) echo -n $c1$c2$c3 ;; # [guaranteed to be non-empty]
esac
done | tee >(stdbuf -o0 java -jar your_applet.jar)
read is a BASH built-in function that captures keyboard input.
echo is a BASH built-in function that prints what it`s told.
tee redirects the output to both STDOUT and the file provided.
>() is the so-called process substitution; it acts like a file for the writer and passes the resulting file`s contents as input to the command inside its brackets.
stdbuf disables output buffering, just in case.
Both tee and stdbuf are parts of coreutils, so they have to be present almost everywhere (maybe except Android, but that`s another story).
[UPD.] Last line, adapted:
done | tee >(stdbuf -o0 "$JAVA" $JAVA_OPTS -jar "$JBOSS_HOME/jboss-modules.jar" -mp "$JBOSS_MODULEPATH" org.jboss.as.domain-add-user "$#")
Hopefully that`d work.
[UPD2.] FINALLY! A different solution!
Just add this before eval:
stty=$(stty -g)
stty -ctlecho
trap "stty $stty" HUP INT QUIT PIPE TERM
Okay, this is also not perfect (the user is now able to travel the screen by pressing cursor keys…) but that`s still better than ^[[B^[[A^[[D^[[C
Related
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
Is it possible to type / paste something to the console without executing the command? Something that would emulate the normal Ctrl / Cmd + c, Ctrl / Cmd + v where the text is put on the current line without being executed so the user can continue typing, deleting, etc.
For example I have the following simple script:
#!/bin/bash
echo -n "foo" | pbcopy
pbpaste
Now when I run this, it just echoes foo% and goes on the next line.
Also, even if this would work, I would prefer a solution that works on both mac os and ubuntu (as far as I know pbcopy does not come pre-installed on all linux distros).
EDIT
Edit to explain the scenario better. Imagine the script above is called foo.sh. You run the script ./foo.sh and when it finishes you have a new prompt with only the text "foo"|. The | represents the cursor.
So... You already know about pbcopy and pbpaste in macOS. You probably want to know about xclip, which is a similar interface to X selections ("the clipboard") from the command line.
Each system (Aqua and X) handles clipboard data structures differently, and I'm not aware of any single tool which will function this way on both platforms. That said, you can perhaps write scripts that are portable between both systems:
#!/usr/bin/env bash
if type xclip >/dev/null; then
clip_copy="xclip"
clip_paste="xclip -o"
elif type pbcopy >/dev/null ; then
clip_copy="pbcopy"
clip_paste="pbpaste"
else
echo "ERROR: no clipboard functions. Where am I?" >&2
exit 1
fi
Also note that pbcopy/pbpaste support different data types, whereas xclip just deals with text.
That said, both of these functions deal with stdin and stdout. If you want to actually simulate keypresses (as your edit appears to imply), you need another tool.
In the Mac world, a number of options exist. Cliclick works well for me. This tool has full mouse support, but also has an option t:, which will simulate keyboard input. It seems reasonable that one might cliclick t:"$(pbpaste)", though I've never tried it.
You can also use AppleScript to print arbitrary text:
$ osascript -e 'tell application "System Events" to keystroke "Hello world."'
In X, xdotool seems to work.
$ xdotool type "Hello world."
To make a script which might run in both macOS and X environments, you could key on the output of uname -s:
#!/usr/bin/env bash
case "$(uname -s)" in
Darwin)
clip_copy="pbcopy" # note: $clip_copy isn't used in this script.
clip_paste="pbpaste"
type_cmd="osascript -e 'tell application \"System Events\" to keystroke \"%s\"'"
;;
*)
clip_copy="xclip"
clip_paste="xclip -o"
type_cmd='xdotool type "%s"'
;;
esac
text="$($clip_paste)"
printf "$type_cmd" "${text//[!A-Za-z0-9. ]/}" | sh
Note: untested. YMMV. May contain nuts.
Based on a gist in the comments I found a thread on stack exchange that helped me answer my own question: https://unix.stackexchange.com/a/213821.
I am only interested in supporting sh, bash, zsh so the following solution works fine:
#!/bin/bash
if [ "$(echo $ZSH_VERSION)" ]; then
print -z $#
else
# assume bash or sh
bind '"\e[0n": "'"$*"'"'; printf '\e[5n'
fi
Call with source inject.sh echo foo
I need to write the time taken to execute this command in a txt file:
time ./program.exe
How can I do in bash script?
I try with >> time.txt but that doesn't work (the output does not go to file and does go to the screen).
Getting time in bash to write to a file is hard work. It is a bash built-in command. (On Mac OS X, there's an external command, /usr/bin/time, that does a similar job but with a different output format and less recalcitrance.)
You need to use:
(time ./program.exe) 2> time.txt
It writes to standard error (hence the 2> notation). However, if you don't use the sub-shell (the parentheses), it doesn't work; the output still comes to the screen.
Alternatively, and without a sub-shell, you can use:
{ time ./program.exe; } 2> time.txt
Note the space after the open brace and the semi-colon; both are necessary on a single line. The braces must appear where a command could appear, and must be standalone symbols. (If you struggle hard enough, you'll come up with ...;}|something or ...;}2>&1. Both of these identify the brace as a standalone symbol, though. If you try ...;}xyz, the shell will (probably) fail to find a command called }xyz, though.)
I need to run more command in more terminal. If I do this:
xterm -xrm '*hold: true' -e (time ./Program.exe) >> time.exe & sleep 2
it doesn't work and tells me Syntax error: "(" unexpected. How do I fix this?
You would need to do something like:
xterm -xrm '*hold: true' -e sh -c "(time ./Program.exe) 2> time.txt & sleep 2"
The key change is to run the shell with the script coming from the argument to the -c option; you can replace sh with /bin/bash or an equivalent name. That should get around any 'Syntax error' issues. I'm not quite sure what triggers that error, though, so there may be a simpler and better way to deal with it. It's also conceivable that xterm's -e option only takes a single string argument, in which case, I suppose you'd use:
xterm -xrm '*hold: true' -e 'sh -c "(time ./Program.exe) 2> time.txt & sleep 2"'
You can manual bash xterm as well as I can.
I'm not sure why you run the timed program in background mode, but that's your problem, not mine. Similarly, the sleep 2 is not obviously necessary if the hold: true keeps the terminal open.
time_elapsed=(time sh -c "./program.exe") 2>&1 | grep "real" | awk '{print $(NF)}'
echo time_elapsed > file.txt
This command should give you the exact time consumed in bash in a desired file..
You can also redirect this to a file usng 2 > file.txt as explained in another reply.
It's not easy to redirect the output of the bash builtin time.
One solution is to use the external time program:
/bin/time --append -o time.txt ./program.exe
(on most systems it's a GNU program, so use info time rather than man to get its documentation).
Just enclose the command to time in a { .. }:
{ time ./program.exe; } 2>&1
Of course, the output of builtin time goes to stderr, thus the needed redirection 2>&1.
Then, it may appear to be tricky to capture the output, let's use a second { .. } to read the command more easily, this works:
{ { time ./program.exe; } 2>&1; } >> time.txt # This works.
However, the correct construct should simply have the capture reversed, as this:
{ time ./program.exe; } >> time.txt 2>&1; # Correct.
To close any possible output from the command, redirect it's output to /dev/null, as this:
{ time ./program.exe >/dev/null 2>&1; } >> time.txt 2>&1 # Better.
And, as now there is only output on stderr, we could simply capture just it:
{ time ./program.exe >/dev/null 2>&1; } 2>> time.txt # Best.
The output from ./program should be redirected, or it may well end inside time.txt.
I'm currently using the following to capture everything that goes to the terminal and throw it into a log file
exec 4<&1 5<&2 1>&2>&>(tee -a $LOG_FILE)
however, I don't want color escape codes/clutter going into the log file. so i have something like this that sorta works
exec 4<&1 5<&2 1>&2>&>(
while read -u 0; do
#to terminal
echo "$REPLY"
#to log file (color removed)
echo "$REPLY" | sed -r 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g' >> $LOG_FILE
done
unset REPLY #tidy
)
except read waits for carriage return which isn't ideal for some portions of the script (e.g. echo -n "..." or printf without \n).
Follow-up to Jonathan Leffler's answer:
Given the example script test.sh:
#!/bin/bash
LOG_FILE="./test.log"
echo -n >$LOG_FILE
exec 4<&1 5<&2 1>&2>&>(tee -a >(sed -r 's/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g' > $LOG_FILE))
##### ##### #####
# Main
echo "starting execution"
printf "\n\n"
echo "color test:"
echo -e "\033[0;31mhello \033[0;32mworld\033[0m!"
printf "\n\n"
echo -e "\033[0;36mEnvironment:\033[0m\n foo: cat\n bar: dog\n your wife: hot\n fix: A/C"
echo -n "Before we get started. Is the above information correct? "
read YES
echo -e "\n[READ] $YES" >> $LOG_FILE
YES=$(echo "$YES" | sed 's/^\s*//;s/\s*$//')
test ! "$(echo "$YES" | grep -iE '^y(es)?$')" && echo -e "\nExiting... :(" && exit
printf "\n\n"
#...some hundreds of lines of code later...
echo "Done!"
##### ##### #####
# End
exec 1<&4 4>&- 2<&5 5>&-
echo "Log File: $LOG_FILE"
The output to the terminal is as expected and there is no color escape codes/clutter in the log file as desired. However upon examining test.log, I do not see the [READ] ... (see line 21 of test.sh).
The log file [of my actual bash script] contains the line Log File: ... at the end of it even after closing the 4 and 5 fds. I was able to resolve the issue by putting a sleep 1 before the second exec - I assume there's a race condition or fd shenanigans to blame for it. Unfortunately for you guys, I am not able to reproduce this issue with test.sh but I'd be interested in any speculation anyone may have.
Consider using the pee program discussed in Is it possible to distribute stdin over parallel processes. It would allow you to send the log data through your sed script, while continuing to send the colours to the actual output.
One major advantage of this is that it would remove the 'execute sed once per line of log output'; that is really diabolical for performance (in terms of number of processes executed, if nothing else).
I know it's not a perfect solution, but cat -v will make non visible chars like \x1B to be converted into visible form like ^[[1;34m. The output will be messy, but it will be ascii text at least.
I use to do stuff like this by setting TERM=dumb before running my command. That pretty much removed any control characters except for tab, CR, and LF. I have no idea if this works for your situation, but it's worth a try. The problem is that you won't see color encodings on your terminal either since it's a dumb terminal.
You can also try either vis or cat (especially the -v parameter) and see if these do something for you. You'd simply put them in your pipeline like this:
exec 4<&1 5<&2 1>&2>&>(tee -a | cat -v | $LOG_FILE)
By the way, almost all terminal programs have an option to capture the input, and most clean it up for you. What platform are you on, and what type of terminal program are you using?
You could attempt to use the -n option for read. It reads in n characters instead of waiting for a new line. You could set it to one. This would increase the number of iteration the code runs, but it would not wait for newlines.
From the man:
-n NCHARS read returns after reading NCHARS characters rather than waiting for a complete line of input.
Note: I have not tested this
You can use ANSIFilter to strip or transform console output with ANSI escape sequences.
See http://www.andre-simon.de/zip/download.html#ansifilter
Might not screen -L or the script commands be viable options instead of this exec loop?
So this is probably an easy question, but I am not much of a bash programmer and I haven't been able to figure this out.
We have a closed source program that calls a subprogram which runs until it exits, at which point the program will call the subprogram again. This repeats indefinitely.
Unfortunately the main program will sometimes spontaneously (and repeatedly) fail to call the subprogram after a random period of time. The eventual solution is to contact the original developers to get support, but in the meantime we need a quick hotfix for the issue.
I'm trying to write a bash script that will monitor the output of the program and when it sees a specific string, it will restart the machine (the program will run again automatically on boot). The bash script needs to pass all standard output through to the screen up until it sees the specific string. The program also needs to continue to handle user input.
I have tried the following with limited success:
./program1 | ./watcher.sh
watcher.sh is basically just the following:
while read line; do
echo $line
if [$line == "some string"]
then
#the reboot script works fine
./reboot.sh
fi
done
This seems to work OK, but leading whitespace is stripped on the echo statement, and the echo output hangs in the middle until subprogram exits, at which point the rest of the output is printed to the screen. Is there a better way to accomplish what I need to do?
Thanks in advance.
I would do something along the lines of:
stdbuf -o0 ./program1 | grep --line-buffered "some string" | (read && reboot)
you need to quote your $line variable, i.e. "$line" for all references *(except the read line bit).
Your program1 is probably the source of the 'paused' data. It needs to flush its output buffer. You probably don't have control of that, so
a. check if your system has unbuffer command available. If so try unbuffer cmd1 | watcher You may have to experiment with which cmd you wrap unbuffer with, maybe you whill have to do cmd1 | unbuffer watcher.
b. OR you can try wrapping watcher as a process-group, (I think that is the right terminology), i.e.
./program1 | { ./watcher.sh ; printf "\n" ; }
I hope this helps.
P.S. as you appear to be a new user, if you get an answer that helps you please remember to mark it as accepted, and/or give it a + (or -) as a useful answer.
use read's $REPLY variable, also I'd suggest using printf instead of echo
while read; do
printf "%s\n" "$REPLY"
# '[[' is Bash, quotes are not necessary
# use '[ "$REPLY" == "some string" ]' if in another shell
if [[ $REPLY == "some string" ]]
then
#the reboot script works fine
./reboot.sh
fi
done