Shorthand for declaring a boolean value in bash - bash

Say I have this bash function:
foobar_force(){
foorbar "$#" --force
}
foobar () {
local is_force=$(test "$2" == "--force");
}
I am looking for two things - I would like to only test if the last argument is "--force" not just the second argument, if possible.
Also I am looking for some shorthand I can use to declare is_force as true or false.
I guess the only way I know how to do that is to use:
if [[ "$2" == "--force" ]]; then
local is_force="yes" ;
fi

Try this...
$ cat example.sh
foobar () {
[[ "${#: -1}" == "--force" ]] && local is_force="true" || local is_force="false";
}
foobar_force(){
foobar "$#" --force
}

Related

Using a function locally to checks blank strings

Cannot understand why the function myfunc fails. I put a string in src and dst, and I get the message
src is blank
dst is blank
Here is the code
zblank ()
{
local s="$1"
declare -i f=1 num=0
[[ $s =~ ^[[:space:]]*$ ]] && num=1
return $num
}
myfunc
{
src="angio"
dst="angio"
(
function myzblank() { zblank "$#"; }
myzblank "$src" && printf '%s\n' "src is blank"
myzblank "$dst" && printf '%s\n' "dst is blank"
)
}
When you use return $num, the result is interpreted as an exit status.
Exit status 0 means success.
Every nonzero exit status means failure.
Thus, when you set num=1 only if [[ $s =~ ^[[:space:]]*$ ]], you're reporting success unless that happens, so the && branch is executed.
If you want zblank to return success only if the variable you pass it is blank, it should instead look like:
zblank() { [[ $1 =~ ^[[:space:]]*$ ]]; }
You don't need return: By default the return value of a function is that of the last command it ran.
You don't need $s: it doesn't make things any terser or clearer than $1.
You don't need declare -i: When a variable is used in an arithmetic context its value is treated as numeric even if it was a string previously.
And if you wanted it to return success only in the case where $1 is not all-spaces, you could write it as:
znonblank() { ! [[ $1 =~ ^[[:space:]]*$ ]]; }
...addition of a leading ! being the only change needed.
All that said -- you don't need bash extensions like [[ ]] to do this at all. POSIX sh is powerful enough for what you're doing here:
zblank() {
case $1 in
*[![:space:]]*) return 1;; # At least one non-space character exists
*) return 0;; # No non-spaces exist; success.
esac
}

Can I loop over the keys of an associative array with the same syntax as bash and zsh

I want to loop over the keys of an associative array, but I would like my script to work in zsh and bash. Would there be a way of doing it (syntax) that it would work in both
I know this
zsh:
for k in "${(k)array[#]}" do echo $k; done
bash:
for k in "${!array[#]}" do echo $k; done
Btw. I actually use oh-my-zsh, so I cannot really use something as emulate ksh, because it makes my terminal crash.
You can wrap each implementation in a function, and define only the function appropriate to the current shell:
if [[ $ZSH_VERSION ]]; then
keys_for_array() {
local array=$1 dest=$2
[[ $1 && $2 ]] || { echo "Usage: keys_for_array source-array dest-array" >&2; return 1; }
: ${(AP)dest::=${(kP)array}}
}
elif [[ $BASH_VERSION && ! $BASH_VERSION =~ ^([123][.]|4[.][012]) ]]; then
keys_for_array() {
[[ $1 && $2 ]] || { echo "Usage: keys_for_array source-array dest-array" >&2; return 1; }
local -n array=$1 dest=$2
eval 'dest=( "${!array[#]}" )'
}
else
keys_for_array() { echo "ERROR: keys_for_array not available for this shell" >&2; return 1; }
fi
[[ $ZSH_VERSION ]] && typeset -A aa=( 1 one 2 two )
[[ $BASH_VERSION ]] && declare -A aa=( [1]=one [2]=two )
keys_for_array aa aak
declare -p aak
...when run on bash, output is:
declare -a aak=([0]="1" [1]="2")
...when run on zsh, output is:
typeset -a aak=( 1 2 )
In either case, you can then iterate over them:
keys_for_array aa aak
for key in "${aak[#]}"; do
echo "Key $key has value ${aa[$key]}"
done
Note that above, both functions are syntactically valid in both shells. Otherwise, it might be necessary to use eval or source to pull them in only conditionally. Personally, I would create two separate files with both bash and zsh versions of your portability library, and source only the file appropriate to the current shell.
Not enough reputation points to comment on previous answer, but I can answer?
Don't forget to add a '!' to get the keys vs the values.
keys_for_array aa aak
for key in "${!aak[#]}"; do
echo "Key $key has value ${aa[$key]}"
done

Bash - Rewriting the if function

I'm trying to make my own if function, which is written a little differently than the conventional if function.
This is what I currently have (which is probably very far from completion)
function check
{
if [ "$2" = "=" ]; then
if [ "$1" = "$3" ]; then
// Don't know what to put in here
elif [ "$1" != "$3" ]; then
// Don't know what to put in here
fi
elif [ "$2" = "!=" ]; then
if [ "$1" != "$3" ]; then
// Don't know what to put in here
elif [ "$1" = "$3" ]; then
// Don't know what to put in here
fi
fi
}
When completed it should run like:
check $foo != 2
//do this
end
How do I achieve this?
How do I incorporate the indented code? And how do I incorporate the "end" statement?
It sounds like what you're trying to do is better done by replacing test aka [, not if itself. Here's how you'd complete your function:
function check
{
if [ "$2" = "=" ]; then
if [ "$1" = "$3" ]; then
return 0
else
return 1
fi
elif [ "$2" = "!=" ]; then
if [ "$1" != "$3" ]; then
return 0
else
return 1
fi
fi
echo "Unknown operator: $2" >&2
return 1
}
And here's how to use it:
if check "foo" != "bar"
then
echo "it works"
fi
You will never succeed to create in bash a wrapper for if. This because if is a shell keyword.
A keyword is a reserved word, token or operator. Keywords have a
special meaning to the shell, and indeed are the building blocks of
the shell's syntax. As examples, if, for, while, do, and ! are keywords.
Similar to a builtin, a keyword is hard-coded into Bash, but unlike a
builtin, a keyword is not in itself a command, but a subunit of a
command construct.
Here is a demonstration:
$ type if
if is a shell keyword
$ function if { echo "something"; }
$ type if
if is a shell keyword
$ #so, 'if' has remained a shell keyword
$ #and the new created function 'if' will never work :(
$ type cd
cd is a shell builtin
$ function cd { echo "something"; }
$ type cd
cd is a function
cd ()
{
echo "something"
}
$ cd $HOME
something
$ #so, 'cd' is not anymore a builtin and the new created function 'cd' works! :)

Customize "cd" in bash

I just think that it is convenient for me to "cd" to the directory where I store some file, ie.
[admin#local /]$ cd /usr/bin/somefile.pl
which as far as I know that the official "cd" command will not work.
so I wrote something like this:
main () {
if [[ "${1}" =~ "(.+/)*(.*){1}" ]] && [ -f "${1}" ] ; then
`\cd ${1%/*}`
elif [ -f "${1}" ] ; then
exit 0
else ; `\cd ${1}`
fi
}
main ${1}
and I alias this cd.sh to the "cd" command:
alias cd='source /somepath/cd.sh'
and this doesn't work.
I've tried to use eval "\cd xxx" instead of just \cd xxx;
How can I fix my script?
It feels like a bad idea to override cd, so I'll suggest a slightly different command, fcd:
fcd() { cd -- "$(dirname -- "$1")"; }
$ fcd /usr/bin/somefile.pl
$ pwd
/usr/bin
Or using parameter expansion to save a call to dirname:
fcd { cd -- "${1%/*}"; }
cd() {
DN="$(dirname "$1")"
if [[ -d "$1" ]]; then
builtin cd "$1"
elif [[ -d "$DN" ]]; then
builtin cd "$DN"
else
echo "$* or $DN: No such directories"
return 1
fi
return 0
}

Determine if a function exists in bash

Currently I'm doing some unit tests which are executed from bash. Unit tests are initialized, executed and cleaned up in a bash script. This script usualy contains an init(), execute() and cleanup() functions. But they are not mandatory. I'd like to test if they are or are not defined.
I did this previously by greping and seding the source, but it seemed wrong. Is there a more elegant way to do this?
Edit: The following sniplet works like a charm:
fn_exists()
{
LC_ALL=C type $1 | grep -q 'shell function'
}
Like this: [[ $(type -t foo) == function ]] && echo "Foo exists"
The built-in type command will tell you whether something is a function, built-in function, external command, or just not defined.
Additional examples:
$ LC_ALL=C type foo
bash: type: foo: not found
$ LC_ALL=C type ls
ls is aliased to `ls --color=auto'
$ which type
$ LC_ALL=C type type
type is a shell builtin
$ LC_ALL=C type -t rvm
function
$ if [ -n "$(LC_ALL=C type -t rvm)" ] && [ "$(LC_ALL=C type -t rvm)" = function ]; then echo rvm is a function; else echo rvm is NOT a function; fi
rvm is a function
The builtin bash command declare has an option -F that displays all defined function names. If given name arguments, it will display which of those functions exist, and if all do it will set status accordingly:
$ fn_exists() { declare -F "$1" > /dev/null; }
$ unset f
$ fn_exists f && echo yes || echo no
no
$ f() { return; }
$ fn_exist f && echo yes || echo no
yes
If declare is 10x faster than test, this would seem the obvious answer.
Edit: Below, the -f option is superfluous with BASH, feel free to leave it out. Personally, I have trouble remembering which option does which, so I just use both. -f shows functions, and -F shows function names.
#!/bin/sh
function_exists() {
declare -f -F $1 > /dev/null
return $?
}
function_exists function_name && echo Exists || echo No such function
The "-F" option to declare causes it to only return the name of the found function, rather than the entire contents.
There shouldn't be any measurable performance penalty for using /dev/null, and if it worries you that much:
fname=`declare -f -F $1`
[ -n "$fname" ] && echo Declare -f says $fname exists || echo Declare -f says $1 does not exist
Or combine the two, for your own pointless enjoyment. They both work.
fname=`declare -f -F $1`
errorlevel=$?
(( ! errorlevel )) && echo Errorlevel says $1 exists || echo Errorlevel says $1 does not exist
[ -n "$fname" ] && echo Declare -f says $fname exists || echo Declare -f says $1 does not exist
Borrowing from other solutions and comments, I came up with this:
fn_exists() {
# appended double quote is an ugly trick to make sure we do get a string -- if $1 is not a known command, type does not output anything
[ `type -t $1`"" == 'function' ]
}
Used as ...
if ! fn_exists $FN; then
echo "Hey, $FN does not exist ! Duh."
exit 2
fi
It checks if the given argument is a function, and avoids redirections and other grepping.
Dredging up an old post ... but I recently had use of this and tested both alternatives described with :
test_declare () {
a () { echo 'a' ;}
declare -f a > /dev/null
}
test_type () {
a () { echo 'a' ;}
type a | grep -q 'is a function'
}
echo 'declare'
time for i in $(seq 1 1000); do test_declare; done
echo 'type'
time for i in $(seq 1 100); do test_type; done
this generated :
real 0m0.064s
user 0m0.040s
sys 0m0.020s
type
real 0m2.769s
user 0m1.620s
sys 0m1.130s
declare is a helluvalot faster !
Testing different solutions:
#!/bin/bash
test_declare () {
declare -f f > /dev/null
}
test_declare2 () {
declare -F f > /dev/null
}
test_type () {
type -t f | grep -q 'function'
}
test_type2 () {
[[ $(type -t f) = function ]]
}
funcs=(test_declare test_declare2 test_type test_type2)
test () {
for i in $(seq 1 1000); do $1; done
}
f () {
echo 'This is a test function.'
echo 'This has more than one command.'
return 0
}
post='(f is function)'
for j in 1 2 3; do
for func in ${funcs[#]}; do
echo $func $post
time test $func
echo exit code $?; echo
done
case $j in
1) unset -f f
post='(f unset)'
;;
2) f='string'
post='(f is string)'
;;
esac
done
outputs e.g.:
test_declare (f is function)
real 0m0,055s user 0m0,041s sys 0m0,004s exit code 0
test_declare2 (f is function)
real 0m0,042s user 0m0,022s sys 0m0,017s exit code 0
test_type (f is function)
real 0m2,200s user 0m1,619s sys 0m1,008s exit code 0
test_type2 (f is function)
real 0m0,746s user 0m0,534s sys 0m0,237s exit code 0
test_declare (f unset)
real 0m0,040s user 0m0,029s sys 0m0,010s exit code 1
test_declare2 (f unset)
real 0m0,038s user 0m0,038s sys 0m0,000s exit code 1
test_type (f unset)
real 0m2,438s user 0m1,678s sys 0m1,045s exit code 1
test_type2 (f unset)
real 0m0,805s user 0m0,541s sys 0m0,274s exit code 1
test_declare (f is string)
real 0m0,043s user 0m0,034s sys 0m0,007s exit code 1
test_declare2 (f is string)
real 0m0,039s user 0m0,035s sys 0m0,003s exit code 1
test_type (f is string)
real 0m2,394s user 0m1,679s sys 0m1,035s exit code 1
test_type2 (f is string)
real 0m0,851s user 0m0,554s sys 0m0,294s exit code 1
So declare -F f seems to be the best solution.
It boils down to using 'declare' to either check the output or exit code.
Output style:
isFunction() { [[ "$(declare -Ff "$1")" ]]; }
Usage:
isFunction some_name && echo yes || echo no
However, if memory serves, redirecting to null is faster than output substitution (speaking of, the awful and out-dated `cmd` method should be banished and $(cmd) used instead.) And since declare returns true/false if found/not found, and functions return the exit code of the last command in the function so an explicit return is usually not necessary, and since checking the error code is faster than checking a string value (even a null string):
Exit status style:
isFunction() { declare -Ff "$1" >/dev/null; }
That's probably about as succinct and benign as you can get.
From my comment on another answer (which I keep missing when I come back to this page)
$ fn_exists() { test x$(type -t $1) = xfunction; }
$ fn_exists func1 && echo yes || echo no
no
$ func1() { echo hi from func1; }
$ func1
hi from func1
$ fn_exists func1 && echo yes || echo no
yes
Invocation of a function if defined.
Known function name. Let's say the name is my_function, then use
[[ "$(type -t my_function)" == 'function' ]] && my_function;
# or
[[ "$(declare -fF my_function)" ]] && my_function;
Function's name is stored in a variable. If we declare func=my_function, then we can use
[[ "$(type -t $func)" == 'function' ]] && $func;
# or
[[ "$(declare -fF $func)" ]] && $func;
The same results with || instead of &&
(Such a logic inversion could be useful during coding)
[[ "$(type -t my_function)" != 'function' ]] || my_function;
[[ ! "$(declare -fF my_function)" ]] || my_function;
func=my_function
[[ "$(type -t $func)" != 'function' ]] || $func;
[[ ! "$(declare -fF $func)" ]] || $func;
Strict mode and precondition checks
We have set -e as a strict mode.
We use || return in our function in a precondition.
This forces our shell process to be terminated.
# Set a strict mode for script execution. The essence here is "-e"
set -euf +x -o pipefail
function run_if_exists(){
my_function=$1
[[ "$(type -t $my_function)" == 'function' ]] || return;
$my_function
}
run_if_exists non_existing_function
echo "you will never reach this code"
The above is an equivalent of
set -e
function run_if_exists(){
return 1;
}
run_if_exists
which kills your process.
Use || { true; return; } instead of || return; in preconditions to fix this.
[[ "$(type -t my_function)" == 'function' ]] || { true; return; }
This tells you if it exists, but not that it's a function
fn_exists()
{
type $1 >/dev/null 2>&1;
}
fn_exists()
{
[[ $(type -t $1) == function ]] && return 0
}
update
isFunc ()
{
[[ $(type -t $1) == function ]]
}
$ isFunc isFunc
$ echo $?
0
$ isFunc dfgjhgljhk
$ echo $?
1
$ isFunc psgrep && echo yay
yay
$
I would improve it to:
fn_exists()
{
type $1 2>/dev/null | grep -q 'is a function'
}
And use it like this:
fn_exists test_function
if [ $? -eq 0 ]; then
echo 'Function exists!'
else
echo 'Function does not exist...'
fi
I particularly liked solution from Grégory Joseph
But I've modified it a little bit to overcome "double quote ugly trick":
function is_executable()
{
typeset TYPE_RESULT="`type -t $1`"
if [ "$TYPE_RESULT" == 'function' ]; then
return 0
else
return 1
fi
}
It is possible to use 'type' without any external commands, but you have to call it twice, so it still ends up about twice as slow as the 'declare' version:
test_function () {
! type -f $1 >/dev/null 2>&1 && type -t $1 >/dev/null 2>&1
}
Plus this doesn't work in POSIX sh, so it's totally worthless except as trivia!
You can check them in 4 ways
fn_exists() { type -t $1 >/dev/null && echo 'exists'; }
fn_exists() { declare -F $1 >/dev/null && echo 'exists'; }
fn_exists() { typeset -F $1 >/dev/null && echo 'exists'; }
fn_exists() { compgen -A function $1 >/dev/null && echo 'exists'; }

Resources