Test if program exists in Bash Script - abbreviated version - bash

I want to use the abbreviated if then else to determine if ccze exists before using it... and I just cannot get that first part right...
test() {
[ $(hash ccze) 2>/dev/null ] && echo "yes" || echo "no"
}
The above is just test.. what am I doing wrong? It does not matter if ccze exists or not - I'm getting "no"

testcmd () {
command -v "$1" >/dev/null
}
Using it:
if testcmd hello; then
echo 'hello is in the path'
else
echo 'hello is not in the path'
fi
or
testcmd man && echo yes || echo no
or you could put that into a function:
ptestcmd () {
testcmd "$1" && echo 'yes' || echo 'no'
}
This way you'll have one function that does testing and a separate function that does printing dependent on the test result. You may then use the one taht is appropriate for the situation (you may not always want output).

Suggestion #1 - Answer
some_function() {
hash ccze &> /dev/null && echo "yes" || echo "no"
}
Suggestion #2
Rename the function to something else because there is already the command test.
Don't override.
Suggestion #3
Remove the square brackets. Even if, in any case, you want to use square brackets, use double square brackets. Single square brackets are deprecated.
Brain Food
Shellcheck
Check for a command in PATH
Obsolete and Deprecated Bash Syntax
hash

test() #(not a good name)
{
if hash ccze 2>/dev/null; then
echo yes
else
echo no
fi
}
should do it.
command -v ccze >/dev/null is also usable and fairly portable (not POSIX but works with most shells).

Thanks for that but it was the one liner I wanted. I know that version works.
Meanwhile we figured it out...
test() { [ -x "$(command -v cckze)" ] && echo "yes" || echo "no"; }
Thanks anyway.

Related

How to interpret if "$#" in bash?

I'm trying to understand this bash code but I'm pretty new on this. I'm not sure how to interpreter the next snippet. In more specific way I have doubts with the if "#"; then line.
check() {
LABEL=$1
shift
echo -e "\nšŸ§Ŗ Testing $LABEL"
if "$#"; then
echo "āœ… Passed!"
return 0
else
echoStderr "āŒ $LABEL check failed."
FAILED+=("$LABEL")
return 1
fi
}
I think that is just like the python list syntax to test an empty list the if will be success when the command tested return something. But I have doubts because bash use a lot error signals and I may be missing something. The use case is:
check "distro" lsb_release -c
check "color" [ $(cat /tmp/color.txt | grep red) ]
The snippeds were taken from this repository
related question:
What does mean $# in bash script

How to do a try catch in bash scripts with curly braces?

A recommended pattern that mimics try-catch in bash scripts is to use curly braces. This does not work as expected, though. The following script outputs A B 1. Why is that the case and how must the script be modified to output the intended A C 1?
#!/usr/bin/env bash
set -e
{
echo "A"
false
echo "B"
} || echo "C"
{
echo "1"
false
echo "2"
}
There are many issues with the 'set -e' - it does not work well in many error conditions. This has been covered in many posting, just search 'errexit bash'. For example: Bash subshell errexit semantics
At this time, there is no clean solution. However, there are good news. I'm working on a proposed change that will allow the above to work. See discussion in bash-bug archive: https://lists.gnu.org/archive/html/bug-bash/2022-07/index.html
And proposal: https://lists.gnu.org/archive/html/bug-bash/2022-07/msg00006.html
Final proposal for the 'errfail' can be found: https://lists.gnu.org/archive/html/bug-bash/2022-07/msg00055.html
I expect to have the solution submitted for review by the bash dev team this week. Hopefully it will get accepted into next bash release.
It will support new 'errfail' option
set -o errfail
{ echo BEFORE ; false ; echo AFTER ; } || echo "CATCH"
and will output: BEFORE CATCH
If you are looking for more fancy solution consider:
alias try=''
alias catch='||'
try {
echo BEFORE
false
echo AFTER
} catch { echo CATCH ; }
A work-around using 2 separate bash invokations:
bash -c 'set -e; echo "A"; false; echo "B"' || echo 'C'
bash -c 'set -e; echo "1"; false; echo "2"'
Or with intendations:
bash -c '
set -e
echo "A"
false
echo "B"
' || echo 'C'
bash -c '
set -e
echo "1"
false
echo "2"
'
Output:
A
C
1
Code Demo
Just use && and remove this dangerous hardly predictable set -e.
#!/bin/sh
{
echo "A" &&
false &&
echo "B"
} || echo "C"
{
echo "1" &&
false &&
echo "2"
}
Test run it
There is never anything wrong with being absolutely explicit in code.
Don't count on the implementation to handle even part of the logic.
This is what set -e is doing. Handles some logic for you, and it does it very differently from what you'd expect it to be most of the time.
So be always explicit and handle error conditions explicitly.

Passing Command to Function in Bash

I am creating a setup bash script so I can quickly setup my servers.
Inside the run function I want to pass the command, which the run function will then verify whether is successful.
function run () {
if output=$( $1 );
then printf 'OK. (Ā«%sĀ»)\n' "$output";
else printf 'Failed! (Ā«%sĀ»)\n' "$output";
fi
}
printf 'Setting up Ā«uniĀ» as system group...'
run " if [ ! $( getent group uni ) ];
then sudo addgroup --system uni;
else echo 'Group exists.';
fi "
However this results in a error: setup.sh: line 5: if: command not found
When I do this it works fine, but I want to eliminate repetitive code as I ha ve many commands:
if output=$(
if [ ! $( getent group uni ) ]; then sudo addgroup --system uni; else echo 'Group exists.'; fi
);
then printf 'OK. (Ā«%sĀ»)\n' "$output";
else printf 'Failed! (Ā«%sĀ»)\n' "$output";
fi
What am I doing wrong?
The safe way to pass code to functions is... to encapsulate that code in another function.
run() {
local output
if output=$("$#"); then
printf 'OK. (Ā«%sĀ»)\n' "$output";
else
printf 'Failed! (Ā«%sĀ»)\n' "$output";
fi
}
printf 'Setting up Ā«uniĀ» as system group...'
step1() {
if [ ! "$(getent group uni)" ]; then
sudo addgroup --system uni;
else
echo 'Group exists.';
fi
}
run step1
If your code didn't involve flow control operators (and otherwise fit into the definition of a "simple command"), you wouldn't even need to do that; with the above run definition (using "$#" instead of $1),
run sudo addgroup --system uni
...would work correctly as-is.
Using either eval or sh -c exposes you to serious security problems; see BashFAQ #48 for a high-level overview, and see BashFAQ #50 for a discussion of why code shouldn't be passed around as text (and preferred ways to avoid the need to do so in the first place!)
You have chosen to pass a shell command string to your function (aka system(3) semantics). This means that you have to use eval to evaluate it:
function run () {
if output=$( eval "$1" );
then printf 'OK. (Ā«%sĀ»)\n' "$output";
else printf 'Failed! (Ā«%sĀ»)\n' "$output";
fi
}
Note that the parameter with your command will be expanded as normal, so if you want the $(getent) to be evaluated in the run function instead of before it, you will need to escape it:
run " if [ ! \$( getent group uni ) ];
then sudo addgroup --system uni;
else echo 'Group exists.';
fi "
If you want to pass a bash piece of code as a function args to be evaluated in a subshell and test the success, you can do it like that:
#!/bin/bash
run () {
# Here I'm not sure what you want to test, so I assume that you want to test if the piece of bash pass as an arg is failing or not
# Do not forget to quote the value in order to take all the string
# You can also try with "eval" instead of "bash -c"
if output=$( bash -c "${1}" ); then
echo "OK. (Ā«${output}Ā»)"
else
echo "Failed! (Ā«${output}Ā»)"
fi
}
# Do not forget to escape the $ here to be evaluated after in the `run` function
# Do not forget to exit with a code different of 0 to indicate there is a failure for the test in the "run" function
run "if [[ ! \$( getent group uni ) ]]; then sudo addgroup --system uni else echo 'Group exists.'; exit 1; fi"
Warning: I posted this piece of code to make your script works the way you seems to want (have a function which will eval your code from a string and interpret the result) but it's not safe at all for this reasons as Charles Duffy pointed out in the comment section.
It's probably safer to make something like:
#!/bin/bash
run () {
if output=$("${#}"); then
echo "OK. (Ā«${output}Ā»)"
else
echo "Failed! (Ā«${output}Ā»)"
fi
}
one_of_your_functions_you_want_to_eval() {
if [[ ! $( getent group uni ) ]]; then
sudo addgroup --system uni
else
echo 'Group exists.'
# do not forget to exit with something different than 0 here
exit 1
fi
}
run one_of_your_functions_you_want_to_eval
Note: in order to declare a function, either you declare it like that (POSIX compliant syntax):
your_function() {
}
Either using a bash only syntax (I think it's better to avoid "bashism" when it not bring real values to your scripts):
function your_function {
}
But no need to mix both syntaxes ;)

Is there a Bash wrapper (program/script) that enables a more succinct input when I want multiple outputs in one Bash call

I'm currently creating monstrosities like the following:
ll /home && echo -e "==============\n" && getent passwd && echo -e "==============\n" && ll /opt/tomcat/ && echo -e "==============\n" && ll /etc/sudoers.d/
Is there perhaps some program that handles this in a nicer way?
Something like this (the hypothetical name of the program would be multiprint in my example):
multiprint --delim-escapechars true --delim "============\n" '{ll /home},{getent passwd},...'
alternatively:
multiprint -de "============\n" '{ll /home},{getent passwd},...'
A function like the following would give you that ability :
function intersect() {
delim=$1
shift
for f; do cat "$f"; echo "$delim"; done
}
You could call it as follows to implement your specific use-case :
intersect '==============' <(ll /home) <(getent passwd) <(ll /opt/tomcat/) <(ll /etc/sudoers.d/)
You can try it here.
printf will repeat its format until its arguments are exhausted. You could write something like
printf '%s\n================\n' "$(ll /home)" "$(getent passed)" "$(ll /opt/tomcat)" "$(ll /etc/sudoers.d)"
although this is a little memory-intensive, since it buffers all the output in memory until all the commands have completed.
Based on #Aaron's answer I ended up creating this multiprint.sh Bash shell script, and for what it's worth posting it here:
#!/bin/bash
# Print output of multiple commands, delimited by a specified string
function multiprint() {
if [[ -z "$*" ]]; then
__multiprint_usage
return 0
elif [[ "$1" == "--help" ]]; then
__multiprint_usage
return 0
else
delim=$1
shift
for f; do cat "$f"; echo -e "$delim"; done
fi
}
function __multiprint_usage() {
echo "Usage:"
echo " multiprint '<delimiter>' <(cmd1) <(cmd2) ..."
# example: multiprint '\n\n\n' <(ll /home/) <(ll /var/) ..."
}

Can I pass an arbitrary block of commands to a bash function?

I am working on a bash script where I need to conditionally execute some things if a particular file exists. This is happening multiple times, so I abstracted the following function:
function conditional-do {
if [ -f $1 ]
then
echo "Doing stuff"
$2
else
echo "File doesn't exist!"
end
}
Now, when I want to execute this, I do something like:
function exec-stuff {
echo "do some command"
echo "do another command"
}
conditional-do /path/to/file exec-stuff
The problem is, I am bothered that I am defining 2 things: the function of a group of commands to execute, and then invoking my first function.
I would like to pass this block of commands (often 2 or more) directly to "conditional-do" in a clean manner, but I have no idea how this is doable (or if it is even possible)... does anyone have any ideas?
Note, I need it to be a readable solution... otherwise I would rather stick with what I have.
This should be readable to most C programmers:
function file_exists {
if ( [ -e $1 ] ) then
echo "Doing stuff"
else
echo "File $1 doesn't exist"
false
fi
}
file_exists filename && (
echo "Do your stuff..."
)
or the one-liner
file_exists filename && echo "Do your stuff..."
Now, if you really want the code to be run from the function, this is how you can do that:
function file_exists {
if ( [ -e $1 ] ) then
echo "Doing stuff"
shift
$*
else
echo "File $1 doesn't exist"
false
fi
}
file_exists filename echo "Do your stuff..."
I don't like that solution though, because you will eventually end up doing escaping of the command string.
EDIT: Changed "eval $*" to $ *. Eval is not required, actually. As is common with bash scripts, it was written when I had had a couple of beers ;-)
One (possibly-hack) solution is to store the separate functions as separate scripts altogether.
The cannonical answer:
[ -f $filename ] && echo "it has worked!"
or you can wrap it up if you really want to:
function file-exists {
[ "$1" ] && [ -f $1 ]
}
file-exists $filename && echo "It has worked"

Resources