how to check existence of a command in bash? - bash

I am trying to write a configuration script which needs to install few packages only if they are not already present so I tried the following ways which worked but I get the error on my screen that the 'command not found'
I tried :
if ! type "$foobar_command_name" > /dev/null; then
# install foobar here
fi
and
function test {
"$#"
local status=$?
if [ $status -ne 0 ]; then
#install this package
fi
return $status
}
test command1
test command2

You would want to redirect stderr to /dev/null as well (not just stdout).
For this, you can use the 2>&1 redirect which joins stderr into stdout, which you then redirect to /dev/null.
The following should work:
if ! type "$foobar_command_name" >/dev/null 2>&1; then
# install foobar here
fi
Note that instead of doing it this way, you could also do it as:
if ! type "$foobar_command_name" >/dev/null 2>/dev/null; then
# install foobar here
fi

Related

Why doesn't testing exit status work when output is redirected?

I have been testing bash and found the following code:
#!/bin/bash
[[ `which pacman` ]] && echo pacman
[[ `which apt-get` ]] && echo apt-get
It will test what package manager is installed and echo it.
On some systems, a failed which command prints the error to stderr. I want to suppress all output from the which command.
So I came up with the following code:
#!/bin/bash
[[ `which pacman >/dev/null 2>&1` ]] && echo pacman
[[ `which apt-get >/dev/null 2>&1` ]] && echo apt-get
But this doesn't output anything. When I run each command on the command line like this:
which pacman >/dev/null 2>&1 && echo $?
On a system with pacman, it prints 0 which it should. The && also proves that the previous command succeeded.
So why doesn't the redirection code work like it does on the command line? What can I add to the script to make it work?
This is really confusing to me, as I have never had this type of problem before. Usually, any command that works on the command line should also work in a bash script, shouldn't it?
[[ ... ]] without a specified test runs [[ -n ... ]]. In this case, it tests whether the captured output is non-empty. But if you redirect everything to /dev/null, the output is indeed empty!
You don't need to capture the output. which should already return a non-zero status when it cannot find the file to execute.
which pacman &> /dev/null && echo pacman

How to output error in custom color in Vagrant provision script?

I want my Vagrant provision script to run some checks that will require user action if they're not satisfied. As easy as:
if [ ! -f /some/required/file ]; then
echo "[Error] Please do required stuff before provisioning"
exit
fi
But, as long as this is not a real error, I got the echo printed in green. I'd like my output to be red (or, a different color at least) to alert the user.
I tried:
echo "\033[31m[Error] Blah blah blah"
that works locally, but on Vagrant output the color code gets escaped and I got it echoed in green instead.
Is that possible?
This is happening because some tools write some of their messages to stderr, which Vagrant then interprets as an error and prints in red.
Not all terminals support ANSI colour codes and Vagrant don't take care of that. Said that, I won't suggest colorizing a word by sending it to stderr unless it is an error.
To achieve that you can simply:
echo "Your error message" > /dev/stderr
You need to use keep_color true then it works as intended;
config.vm.provision "shell", keep_color: true, inline: $echoes
$echoes = <<-ECHOES
echo "\e[32mPROVISIONING DONE\e[0m"
ECHOES
From https://www.vagrantup.com/docs/provisioning/shell.html
keep_color (boolean) - Vagrant automatically colors output in green and red depending on whether the output is from stdout or stderr. If this is true, Vagrant will not do this, allowing the native colors from the script to be outputted.
Vagrant commands runs by default with --no-color option. You could try to set color on with --color. The environmental variables for Vagrant are documented here.
Here is a bash script test.sh which should demonstrate how to output to stderr or stdout conditionally. This form is good for a command like [ / test or touch that does not return any stdout or stderr normally. This form is checking the exit status code of the command which is stored in $?.
test -f $1
if [ $? -eq 0 ]; then
echo "File exists: $1"
else
echo "File not found: $1"
fi
You can alternatively hard code your file path like your question shows:
file="/some/required/file"
test -f $file
if [ $? -eq 0 ]; then
echo "File exists: $file"
else
echo "File not found: $file"
fi
If you have output of the command, but its being sent to stderr rather than stdout and ending up in red in the Vagrant output, you can use the following forms to redirect the output to where you would expect it to be. This is good for commands like update-grub or wget.
wget
url='https://example.com/file'
out=$(wget --no-verbose $url 2>&1)
if [ $? -ne 0 ]; then
echo "$out" > /dev/stderr
else
echo "$out"
fi
update-grub
out=$(update-grub 2>&1)
if [ $? -ne 0 ]; then
echo "$out" > /dev/stderr
else
echo "$out"
fi
One Liners
wget
url='https://example.com/file'
out=$(wget --no-verbose $url 2>&1) && echo "$out" || echo "$out" > /dev/stderr
update-grub
out=$(update-grub 2>&1) && echo "$out" || echo "$out" > /dev/stderr

How to put "> /dev/null 2>&1" into a variable?

How I wish it to work.
if [ $debug = 0 ]; then
silent=""
else
silent='> /dev/null 2>&1'
fi
#some command
wget some.url $silent
So in case $debug is set, it becomes
wget some.url > /dev/null 2>&1
Otherwise if $debug is not set to 1, it becomes
wget some.url
Storing "> /dev/null 2>&1" in a variable doesn't work. How can I do that?
This could be a good excuse to use a shell function:
silent() {
"$#" > /dev/null 2>&1
}
Now you can silence programs by running them with:
silent wget some.url
If you want to only silence things conditionally, that's easy enough:
silent() {
if [[ $debug ]] ; then
"$#"
else
"$#" &>/dev/null
fi
}
You need the shell to actually interpret the variable contents as part of the command line, not just as a string to be passed as an argument to the executable. Check out the eval shell builtin.
Watch out for security holes!
A slightly different approach, but something like this might work:
if [ $debug = 1 ]; then
exec > /dev/null
fi
#some command
wget some.url
It conditionally replaces stdout with /dev/null.
Use eval:
eval wget some.url $silent
eval causes the arguments to be reintepreted as a shell command, rather than as arguments to the program being called.
Be careful; don't run eval on unknown or external input, or you will expose yourself to a big security hole.

Bash command substitution stdout+stderr redirect

Good day. I have a series of commands that I wanted to execute via a function so that I could get the exit code and perform console output accordingly. With that being said, I have two issues here:
1) I can't seem to direct stderr to /dev/null.
2) The first echo line is not displayed until the $1 is executed. It's not really noticeable until I run commands that take a while to process, such as searching the hard drive for a file. Additionally, it's obvious that this is the case, because the output looks like:
sh-3.2# ./runScript.sh
sh-3.2# com.apple.auditd: Already loaded
sh-3.2# Attempting... Enable Security Auditing ...Success
In other words, the stderr was displayed before "Attempting... $2"
Here is the function I am trying to use:
#!/bin/bash
function saveChange {
echo -ne "Attempting... $2"
exec $1
if [ "$?" -ne 0 ]; then
echo -ne " ...Failure\n\r"
else
echo -ne " ...Success\n\r"
fi
}
saveChange "$(launchctl load -w /System/Library/LaunchDaemons/com.apple.auditd.plist)" "Enable Security Auditing"
Any help or advice is appreciated.
this is how you redirect stderr to /dev/null
command 2> /dev/null
e.g.
ls -l 2> /dev/null
Your second part (i.e. ordering of echo) -- It may be because of this you have while invoking the script. $(launchctl load -w /System/Library/LaunchDaemons/com.apple.auditd.plist)
The first echo line is displayed later because it is being execute second. $(...) will execute the code. Try the following:
#!/bin/bash
function saveChange {
echo -ne "Attempting... $2"
err=$($1 2>&1)
if [ -z "$err" ]; then
echo -ne " ...Success\n\r"
else
echo -ne " ...Failured\n\r"
exit 1
fi
}
saveChange "launchctl load -w /System/Library/LaunchDaemons/com.apple.auditd.plist" "Enable Security Auditing"
EDIT: Noticed that launchctl does not actually set $? on failure so capturing the STDERR to detect the error instead.

conditional redirection in bash

I have a bash script that I want to be quiet when run without attached tty (like from cron).
I now was looking for a way to conditionally redirect output to /dev/null in a single line.
This is an example of what I had in mind, but I will have many more commands that do output in the script
#!/bin/bash
# conditional-redirect.sh
if tty -s; then
REDIRECT=
else
REDIRECT=">& /dev/null"
fi
echo "is this visible?" $REDIRECT
Unfortunately, this does not work:
$ ./conditional-redirect.sh
is this visible?
$ echo "" | ./conditional-redirect.sh
is this visible? >& /dev/null
what I don't want to do is duplicate all commands in a with-redirection or with-no-redirection variant:
if tty -s; then
echo "is this visible?"
else
echo "is this visible?" >& /dev/null
fi
EDIT:
It would be great if the solution would provide me a way to output something in "quiet" mode, e.g. when something is really wrong, I might want to get a notice from cron.
For bash, you can use the line:
exec &>/dev/null
This will direct all stdout and stderr to /dev/null from that point on. It uses the non-argument version of exec.
Normally, something like exec xyzzy would replace the program in the current process with a new program but you can use this non-argument version to simply modify redirections while keeping the current program.
So, in your specific case, you could use something like:
tty -s
if [[ $? -eq 1 ]] ; then
exec &>/dev/null
fi
If you want the majority of output to be discarded but still want to output some stuff, you can create a new file handle to do that. Something like:
tty -s
if [[ $? -eq 1 ]] ; then
exec 3>&1 &>/dev/null
else
exec 3>&1
fi
echo Normal # won't see this.
echo Failure >&3 # will see this.
I found another solution, but I feel it is clumsy, compared to paxdiablo's answer:
if tty -s; then
REDIRECT=/dev/tty
else
REDIRECT=/dev/null
fi
echo "Normal output" &> $REDIRECT
You can use a function:
function the_code {
echo "is this visible?"
# as many code lines as you want
}
if tty -s; then # or other condition
the_code
else
the_code >& /dev/null
fi
This works well for me. If DUMP_FILE is empty things go to stdout otherwise to the file. It does the job without using explicit redirection, but just uses pipes and existing applications.
function stdout_or_file
{
local DUMP_FILE=${1:-}
if [ -z "${DUMP_FILE}" ]; then
cat
else
sed -n "w ${DUMP_FILE}"
fi
}
function foo()
{
local MSG=$1
echo "info: ${MSG}"
}
foo "bar" | stdout_or_file ${DUMP_FILE}
Of course, you can squeeze this also in one line
foo "bar" | if [ -z "${DUMP_FILE}" ]; then cat; else sed -n "w ${DUMP_FILE}"; fi
Besides sed -n "w ${DUMP_FILE}" another command that does the same is dd status=none of=${DUMP_FILE}
The simplest solution is to use eval (a shell builtin), as it will act on the redirection in the expanded variable... and also act on anything else in the command line, so add extra quoting as required (note the extra single quotes added around the echo string below due to the '?' which would otherwise cause shell filename expansion to be attempted).
#!/bin/bash
# conditional-redirect.sh
if tty -s; then
REDIRECT=
else
REDIRECT=">& /dev/null"
fi
eval echo '"is this visible?"' $REDIRECT

Resources