This should be OK:
#! /bin/bash -
doSomething() {
print_err "About to do something"
a="$(doSomethingElse)"
if [[ "$a" = "dog" ]]; then
print_err "Worked"
print_err "Now let's do xyz"
printf '%s' "cat"
else
print_err "failed for some reason"
printf '%s' "horse"
fi
}
doSomethingElse() {
print_err "About to do more";
print_err "Done doing more stuff";
printf '%s' 'dog'
}
print_err() {
# Print to stderr as we do this in functions
# that print result to stdout
printf '%s\n' "$1" >&2
}
something="$(doSomething)"
Making it readonly would be somewhat similar to const
readonly something
printf '%s\n' "$something"
Needed:
In general:
Beyond that, replacing [[
with [
would make it sh
compliant. What does it mean to be "sh compatible"?
Also look into scopes of variables. Is there a way to make the local command work portably to dash ksh bash and zsh?
As in, you could have, done:
set_something() {
something='cat'
}
set_something
printf 'something="%s"\n' "$something"
Streams
JavaScript does not work with streams, shell does. Simplified it:
read
from stdin
, file-descriptor 0
(standard in)
write
to stdout
, file-descriptor 1
(standard out)
write
to stderr
, file-descriptor 2
(standard error)
Beyond that one can open new, read / write files etc.
Might of interest How portable are /dev/stdin, /dev/stdout and /dev/stderr?
Return values.
Bash functions does not "return" strings. When one do:
foo="$(some command)"
one assign the standard output of some command
to foo
. If one do not change the redirect of non-data prints in your code, you can see this by:
# In doSomething():
printf 'a="%s"\n' "$a" >&2
At end:
printf '"%s"\n' "$something"
You would get:
a="About to do more
Done doing more stuff
dog"
something="About to do something
failed for some reason
horse"
As you see, the entire output is assigned.
After "fix" one could capture both stdout
and stderr
by redirecting stderr to stdout:
foo="$(some command 2>&1)"
Exit code
Shell functions do, however, have exit codes, just like any program. If not explicitly returning a value, it's the exit status of the last command executed in that function. 0
is OK, anything else is considered an error in tests.
Simple example:
#! /bin/bash -
foo() {
printf 'I am foo\n'
return 1
}
greetings() {
if [ "$1" = "hi" ]; then
printf 'Hello\n'
else
printf 'I do not know what to do with %s\n' "$1"
return 12
fi
}
if foo; then
printf 'OK\n'
else
printf 'FAIL\n'
fi
printf '\n## Try greetings "yo":\n'
greetings yo
ecode=$?
printf 'greetings() returned %d\n' "$ecode"
printf '\n## Try greetings "hi":\n'
greetings hi
ecode=$?
printf 'greetings() returned %d\n' "$ecode"
Result:
I am foo
FAIL
Try greetings "yo":
I do not know what to do with yo
greetings() returned 12
Try greetings "hi":
Hello
greetings() returned 0
Some help by shellcheck
Not fool-proof but can be great help with linting scripts.
You can use it as a command line tool, paste code online, or incorporate it into your editor.
For example if you use Vim with ALE, it picks up shellcheck
if installed. Be aware though that packages bundled with distributions can be somewhat outdated. Ubuntu 18.04, for example, uses v0.4.6
https://github.com/koalaman/shellcheck
Shell scripts in general
Playing around with bash
et al. can be useful for learning. Always consider what you want to do. text-processing for example is almost always better done with other tools. Then one can use shell-scripts to glue it all together. Use pipes etc. - part of the power of the unices. Here under for example sort
, join
, cut
, tr
, paste
, uniq
, find
, grep
, cat
, date
, tail
, head
, xargs
, wc
, fold
, column
, paste
, and so on.
Interactive shell scripts has it use as well as expanding your shell with various functionality.
With more advanced data-types and complexity use things like perl
, python
, and a wide range of others or even c
. c
is in general a very nice way to learn about the system at a lower level.
awk
, sed
etc. are also very powerful and useful for processing text where awk
is the easier to learn.