I am just getting a little more comfortable with Bash.
I wanted a super simple log function to write to a file and output to the console for messages and errors. I also wanted the terminal and the file to interpret newline characters rather than printing them out. Here is what I came up with (it works fine):
#!/bin/bash
log () {
if [[ "3ドル" == '-e' || "3ドル" == '--error' ]]; then
>&2 echo -e "ERROR: 1ドル" && printf "ERROR: 1ドル\n" >> "2ドル"
else
echo -e "1ドル" && printf "1ドル\n" >> "2ドル"
fi
}
# Example implementation
log_file=snip/init.log
msg="\nthis is an error message with leading newline character"
log "$msg" $log_file -e
msg="this is success message with no newline characters"
log "$msg" $log_file
I don't need any more functionality however I have some questions:
- Is an efficient and readable piece of code that won't break when passed strings with special characters?
- Syntactically, could I have done this 'better' such as using pipes instead of
&&.teeinstead ofprintfetc.? - How can I refactor this code to allow for the optional
-eflag to be passed in first?
1 Answer 1
There's quite a bit of repetition:
echo && printwith roughly the same arguments -teeis probably better here- error and non-error paths - perhaps use one function to prepend the
ERROR:tag and call the other?
It might be best to have the filename first, then any number of message words? That would make the interface more like echo, for example.
There's also a problem with our use of printf - any % characters in the error message will be interpreted as replacement indicators. Not what we want. We could use printf %b to expand escape sequences instead:
# log FILE MESSAGE...
log () {
local file="1ドル"; shift
printf '%b ' "$@" '\n' | tee -a "$file"
}
# log_err FILE MESSAGE...
log_error () {
local file="1ドル"; shift
local message="ERROR: 1ドル"; shift
log "$file" "$message" "$@" >&2
}
If we do want to have a single function with arguments (e.g. to add more options), then we might want to use the while/case approach of option parsing that's often used at the program level:
#!/bin/bash
set -eu
# log [-e] [-f FILE] MESSAGE...
log() {
local prefix=""
local stream=1
local files=()
# handle options
while ! ${1+false}
do case "1ドル" in
-e|--error) prefix="ERROR:"; stream=2 ;;
-f|--file) shift; files+=("${1-}") ;;
--) shift; break ;; # end of arguments
-*) log -e "log: invalid option '1ドル'"; return 1;;
*) break ;; # start of message
esac
shift
done
if ${1+false}
then log -e "log: no message!"; return 1;
fi
# if we have a prefix, update our argument list
if [ "$prefix" ]
then set -- "$prefix" "$@"
fi
# now perform the action
printf '%b ' "$@" '\n' | tee -a "${files[@]}" >&$stream
}
There's a non-obvious trick in there that might need explanation: I've used ${1+false} to expand to false when there's at least one argument remaining, or to an empty command when there are no arguments (empty command is considered "true" by if and while).
-
\$\begingroup\$ I think I need to keep this code in one single function though since I will have another log function for logging to only a file (no console), I call that function
log_silent. The thing is I dont really want 4 functions to get this job done. I would like to do this with just two functions. \$\endgroup\$apena– apena2021年02月05日 23:52:15 +00:00Commented Feb 5, 2021 at 23:52 -
1\$\begingroup\$ Yes,
localandtee -aand the newline are all good improvements (I should have tried the code before publishing!). Edited. \$\endgroup\$Toby Speight– Toby Speight2021年02月06日 11:48:07 +00:00Commented Feb 6, 2021 at 11:48 -
1\$\begingroup\$ I've updated to show how you can parse options in a single function. It should be easy to add a "silent" option that sets
stream=/dev/null. \$\endgroup\$Toby Speight– Toby Speight2021年02月06日 11:48:40 +00:00Commented Feb 6, 2021 at 11:48
log [OPTIONS] LOG_FILE MESSAGE...". So when called it would look like thislog "$log_file" "Part1" "Part 2" "and so on"` orlog --error "$log_file" "Part1" "Part 2" "and so on". \$\endgroup\$-efirst, usingifandshift. But it might be better to have two different functions, and that's what I propose in my answer. \$\endgroup\$