unfortunately I'm struggling with some kind of a "simple idea".
Within my Bash Script I'm checking a variable and if it's set I'll print output to the user (kind of verbose / show output).
[ ! -z $boolVerbose ] && fnc_print2user "i" "Print to user in one-line"
That's working pretty fine for me.
Now I have commands which don't allow suppressing their output, so I want to hide it or show it if my variable above is set/not empty.
I tried the following (and several other options..), to run the command as it is (means: showing output), expect the variable is not set/is empty, then it should append "&> /dev/null" to the command before (so suppress the output).
commandWithOutput $([ -z $boolVerbose ] && echo " &> /dev/null")
The script is running fine if I set my verbose Variable, but if it's not set my commandWithOutput throws an error that there're invalid arguments set (if I hard code to suppress every time it's working like charm).
Is it possible to do some kind of dynamic command adjustments like above in a one-liner or do I have to built real if-else statements with different commandWithOutput methods inside?
Thanks in advance for your ideas & help! :-)
I think this roughly gives you what you wanted :
commandWithOutput &> /dev/$([ -z "$boolVerbose" ] && echo null || echo stdout)
Redirect the output to a different file descriptor. Use exec to redirect this descriptor to output or to /dev/null as you need.
#!/bin/bash
for hide in 0 1 ; do
if ((hide)) ; then
exec 3>/dev/null
else
exec 3>&1
fi
echo $hide: something >&3
exec 3>&-
done
Related
I have the following bash script:
echo one
echo two
cd x
echo three
which fails at the 3rd line as there is no directory named x. However, after running the script, when I do $?, 0 is returned, even though the script has an error. How do I detect whether the script ran successfully or not?
Check the condition of directory existence in the script statements:
[ -d x ] && cd x || { echo "no such directory"; exit 1; }
Or put set -e after shebang line:
#!/bin/bash
set -e
echo one
echo two
cd x
echo three
You should end with an exit statement
echo one
echo two
cd x
exitCode=$?
echo three
exit $exitCode;
Then
./myscript
echo $?
1
I have searched all over with no clear answer to this. Put simply it doesn't appear to be a native feature in bash. So I will give you the hard way.
To make a .sh script with multiple commands and you don't know if any will error but you want to check if at least one has. You would need to put a $? at the end of literally every command and redirect it to a text file. Once it's in the text file you could format it like.
Command1 = 0 Success.
Command2 = 127 Fail.
Or you could just add the numbers to the file run it through some kind of calculator to add everything together and if the output is greater than zero then the command at some point failed. But this won't be overly useful if you want the exact number and there are more than one failure.
UPDATED - This is the best way I could find.
You can put this at the top of your script file to catch any errors and exit if it fails.
set -euo pipefail
Feel free to read the manual pages.
Here's my problem, from console if I type the below,
var=`history 1`
echo $var
I get the desired output. But when I do the same inside a shell script, it is not showing any output. Also, for other commands like pwd, ls etc, the script shows the desired output without any issue.
As value of variable contains space, add quotes around it.
E.g.:
var='history 1'
echo $var
I believe all you need is this as follows:
1- Ask user for the number till which user need to print the history in script.
2- Run the script and take Input from user and get the output as follows:
cat get_history.ksh
echo "Enter the line number of history which you want to get.."
read number
if [[ $# -eq 0 ]]
then
echo "Usage of script: get_history.ksh number_of_lines"
exit
else
history "$number"
fi
Added logic where it will check arguments if number of arguments passed is 0 then it will exit from script then.
By default history is turned off in a script, therefore you need to turn it on:
set -o history
var=$(history 1)
echo "$var"
Note the preferred use of $( ) rather than the deprecated backticks.
However, this will only look at the history of the current process, that is this shell script, so it is fairly useless.
I am currently using something like this:
(
false
true
) && echo "OK" || echo "FAILED";
And it doesn't work. I would like the subshell to exit with an error if something fails (false in this case). Currently it only fails if the last command fails.
It should only exit out of the current subshell and not the whole script.
I am giving this script to people and I don't want them to see all the output but still give them some kind of response if the script was successful or not.
Edit: The commands inside the subshell above are only an example. I would like to run multiple commands inside a subshell without checking the return value after each command. Something like set -e for subshells.
Edit2: I tried adding set -e inside a subshell. Maybe I did something wrong but it didn't change the behavior of my script. It didn't stop execution or exit out of the subshell with a non-0 code.
(
set -e
false
echo "test"
) && echo "OK" || echo "FAILED";
First prints test and then OK. It should print FAILED because of false.
This effect of bash set -e inside a conditional expression like foo || true, is known and considered a problem. I think it is good reason to hate both set -e and shell scripting in general.
http://david.rothlis.net/shell-set-e/
http://fvue.nl/wiki/Bash:_Error_handling
The first link makes the following suggestion. It looks good in small examples, but maybe less clear in the real world.
Do your own error checking by stringing together a series of commands
with “&&” like this:
mkdir abc &&
cd abc &&
do_something_else &&
last_thing ||
{ echo error >&2; exit 1; }
A few important notes: You don’t need a trailing backslash. You don’t
indent the following line. You must stick to 80 chars per line so that
everyone can see the “&&” or “||” at the end. If you need to mix ANDs
and ORs, group with { these braces } which don’t spawn a sub-shell.
To your edited question:
Something like set -e for subshells.
Well, you can just do set -e for the subshell.
( set -e
my
commands
)
You can't implicitly make just your subshells have the errexit option. You can do some trickery using eval, or use a subprocess as a shell (even though a subprocess is not the same as a subshell), like
errexit_shell() {
bash -e
}
but those options are both inadvisable for various reasons, not the least of which being readability. Your best bet in that case would just be to adapt your entire script to use set -e, and your subshells will come along for the ride.
To your original question:
Just capture the status of the part that indicates success or failure:
(
cat teisatrt
status=$?
echo "true"
exit "$status"
) && echo passed || echo failed
(Of course, if all you want to know is if that file is readable, don't cat it, just use test -r.)
As you have it, you are redirecting output from the whole subshell to /dev/null, so you will never see your "true" echo. You should move the redirect inside the subshell to the command you really want it on. In order to exit the subshell when cat fails, you will need to check for failure after cat runs. If you don't, then as you have noted, its return code is wiped out by the following statement. So something like this:
echo "Installing App"
(
cat teisatrt &> /dev/null || exit 1
echo "true"
) && echo "OK" || echo "FAILED";
I am trying to echo the last command run inside a bash script. I found a way to do it with some history,tail,head,sed which works fine when commands represent a specific line in my script from a parser standpoint. However under some circumstances I don't get the expected output, for instance when the command is inserted inside a case statement:
The script:
#!/bin/bash
set -o history
date
last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
echo "last command is [$last]"
case "1" in
"1")
date
last=$(echo `history |tail -n2 |head -n1` | sed 's/[0-9]* //')
echo "last command is [$last]"
;;
esac
The output:
Tue May 24 12:36:04 CEST 2011
last command is [date]
Tue May 24 12:36:04 CEST 2011
last command is [echo "last command is [$last]"]
[Q] Can someone help me find a way to echo the last run command regardless of how/where this command is called within the bash script?
My answer
Despite the much appreciated contributions from my fellow SO'ers, I opted for writing a run function - which runs all its parameters as a single command and display the command and its error code when it fails - with the following benefits:
-I only need to prepend the commands I want to check with run which keeps them on one line and doesn't affect the conciseness of my script
-Whenever the script fails on one of these commands, the last output line of my script is a message that clearly displays which command fails along with its exit code, which makes debugging easier
Example script:
#!/bin/bash
die() { echo >&2 -e "\nERROR: $#\n"; exit 1; }
run() { "$#"; code=$?; [ $code -ne 0 ] && die "command [$*] failed with error code $code"; }
case "1" in
"1")
run ls /opt
run ls /wrong-dir
;;
esac
The output:
$ ./test.sh
apacheds google iptables
ls: cannot access /wrong-dir: No such file or directory
ERROR: command [ls /wrong-dir] failed with error code 2
I tested various commands with multiple arguments, bash variables as arguments, quoted arguments... and the run function didn't break them. The only issue I found so far is to run an echo which breaks but I do not plan to check my echos anyway.
Bash has built in features to access the last command executed. But that's the last whole command (e.g. the whole case command), not individual simple commands like you originally requested.
!:0 = the name of command executed.
!:1 = the first parameter of the previous command
!:4 = the fourth parameter of the previous command
!:* = all of the parameters of the previous command
!^ = the first parameter of the previous command (same as !:1)
!$ = the final parameter of the previous command
!:-3 = all parameters in range 0-3 (inclusive)
!:2-5 = all parameters in range 2-5 (inclusive)
!! = the previous command line
etc.
So, the simplest answer to the question is, in fact:
echo !!
...alternatively:
echo "Last command run was ["!:0"] with arguments ["!:*"]"
Try it yourself!
echo this is a test
echo !!
In a script, history expansion is turned off by default, you need to enable it with
set -o history -o histexpand
The command history is an interactive feature. Only complete commands are entered in the history. For example, the case construct is entered as a whole, when the shell has finished parsing it. Neither looking up the history with the history built-in (nor printing it through shell expansion (!:p)) does what you seem to want, which is to print invocations of simple commands.
The DEBUG trap lets you execute a command right before any simple command execution. A string version of the command to execute (with words separated by spaces) is available in the BASH_COMMAND variable.
trap 'previous_command=$this_command; this_command=$BASH_COMMAND' DEBUG
…
echo "last command is $previous_command"
Note that previous_command will change every time you run a command, so save it to a variable in order to use it. If you want to know the previous command's return status as well, save both in a single command.
cmd=$previous_command ret=$?
if [ $ret -ne 0 ]; then echo "$cmd failed with error code $ret"; fi
Furthermore, if you only want to abort on a failed commands, use set -e to make your script exit on the first failed command. You can display the last command from the EXIT trap.
set -e
trap 'echo "exit $? due to $previous_command"' EXIT
Note that if you're trying to trace your script to see what it's doing, forget all this and use set -x.
After reading the answer from Gilles, I decided to see if the $BASH_COMMAND var was also available (and the desired value) in an EXIT trap - and it is!
So, the following bash script works as expected:
#!/bin/bash
exit_trap () {
local lc="$BASH_COMMAND" rc=$?
echo "Command [$lc] exited with code [$rc]"
}
trap exit_trap EXIT
set -e
echo "foo"
false 12345
echo "bar"
The output is
foo
Command [false 12345] exited with code [1]
bar is never printed because set -e causes bash to exit the script when a command fails and the false command always fails (by definition). The 12345 passed to false is just there to show that the arguments to the failed command are captured as well (the false command ignores any arguments passed to it)
I was able to achieve this by using set -x in the main script (which makes the script print out every command that is executed) and writing a wrapper script which just shows the last line of output generated by set -x.
This is the main script:
#!/bin/bash
set -x
echo some command here
echo last command
And this is the wrapper script:
#!/bin/sh
./test.sh 2>&1 | grep '^\+' | tail -n 1 | sed -e 's/^\+ //'
Running the wrapper script produces this as output:
echo last command
history | tail -2 | head -1 | cut -c8-
tail -2 returns the last two command lines from history
head -1 returns just first line
cut -c8- returns just command line, removing PID and spaces.
There is a racecondition between the last command ($_) and last error ( $?) variables. If you try to store one of them in an own variable, both encountered new values already because of the set command. Actually, last command hasn't got any value at all in this case.
Here is what i did to store (nearly) both informations in own variables, so my bash script can determine if there was any error AND setting the title with the last run command:
# This construct is needed, because of a racecondition when trying to obtain
# both of last command and error. With this the information of last error is
# implied by the corresponding case while command is retrieved.
if [[ "${?}" == 0 && "${_}" != "" ]] ; then
# Last command MUST be retrieved first.
LASTCOMMAND="${_}" ;
RETURNSTATUS='✓' ;
elif [[ "${?}" == 0 && "${_}" == "" ]] ; then
LASTCOMMAND='unknown' ;
RETURNSTATUS='✓' ;
elif [[ "${?}" != 0 && "${_}" != "" ]] ; then
# Last command MUST be retrieved first.
LASTCOMMAND="${_}" ;
RETURNSTATUS='✗' ;
# Fixme: "$?" not changing state until command executed.
elif [[ "${?}" != 0 && "${_}" == "" ]] ; then
LASTCOMMAND='unknown' ;
RETURNSTATUS='✗' ;
# Fixme: "$?" not changing state until command executed.
fi
This script will retain the information, if an error occured and will obtain the last run command. Because of the racecondition i can not store the actual value. Besides, most commands actually don't even care for error noumbers, they just return something different from '0'. You'll notice that, if you use the errono extention of bash.
It should be possible with something like a "intern" script for bash, like in bash extention, but i'm not familiar with something like that and it wouldn't be compatible as well.
CORRECTION
I didn't think, that it was possible to retrieve both variables at the same time. Although i like the style of the code, i assumed it would be interpreted as two commands. This was wrong, so my answer devides down to:
# Because of a racecondition, both MUST be retrieved at the same time.
declare RETURNSTATUS="${?}" LASTCOMMAND="${_}" ;
if [[ "${RETURNSTATUS}" == 0 ]] ; then
declare RETURNSYMBOL='✓' ;
else
declare RETURNSYMBOL='✗' ;
fi
Although my post might not get any positive rating, i solved my problem myself, finally.
And this seems appropriate regarding the intial post. :)
I have a script. I would like to give this script a quiet mode and a verbose mode.
This is the equivalent of:
if $verbose
then
redirect="> /dev/null"
fi
echo "Verbose mode enabled" $redirect # This doesn't work because the redirect isn't evaluated.
I'd really like a better way of doing this than writing if-elses for every statement affected.
eval could work, but has obvious side effects on other variables.
You could write a wrapper function:
redirect_cmd() {
# write your test however you want; this just tests if SILENT is non-empty
if [ -n "$SILENT" ]; then
"$#" > /dev/null
else
"$#"
fi
}
You can then use it to run any command with the redirect:
redirect_cmd echo "unsilenced echo"
redirect_cmd ls -d foo*
SILENT=1
redirect_cmd echo "nothing will be printed"
redirect_cmd touch but_the_command_is_still_run
(If all you need to do is echo with this, you can of course make the function simpler, just echoing the first argument instead of running them all as a command)
Got the idea from another question:
#!/bin/sh
if [ $SILENT ]; then
exec &>/dev/null
fi
echo "Silence here."
Not perfect, but how about setting redirect to either "/dev/null" or "/dev/tty", and then doing
{
echo "verbose"
....
} > $redirect
Consider whether it would be worth setting set -x for detailed logging to stderr in verbose mode. If that's so, then verbose-only output can be achieved with a no-op : like this.
while getopts "v" o
do case "$o" in
v) set -x;;
esac
done
echo "This will always be output" # goes to stdout
: this will only be output in verbose mode # goes to stderr
: evaluates it's arguments but does nothing with them.
set -x will show what was evaluated on stderr as each statement is executed.
It also lets you split verbose and standard logs by stream.
Might not be what you need here, but it can be a handy trick.