{{Header}} {{title|title= /bin/bash - Proper Whitespace Handling - Whitespace Safety - End-of-Options Parameter Security }} {{#seo: |description=Supporting multiple command line parameters with spaces in wrapper scripts and Use of End-of-Options Parameter (--). }}
--
) for better security.
}}
= safe echo =
There is no safe usage of echo
, use printf '%s'
instead.
* {{VideoLink
|videoid=lq98MM2ogBk
|text=bash's echo command is broken
}}
* {{VideoLink
|videoid=ft0_cw54qak
|text=echo is broken: a follow-up video
}}
shellcheck bug reports:
* [https://github.com/koalaman/shellcheck/issues/2674 Warn on echo "$var" when $var might be -e #2674]
* [https://github.com/koalaman/shellcheck/issues?q=is%3Aissue+is%3Aopen+echo+in%3Atitle Open shellcheck issues related to echo]
Please note that printf
does not have a default format specifier, but treats the first positional parameter as the format. When the format is missing, the data is treated as if the format specifier is %b
. It is always recommended to be explicit on the format being used to avoid this mistake.
Normally, there is no need to interpret the escape sequences of a variable, therefore use the printf format specifier %s
when the data is not printed to the terminal:
{{CodeSelect|code=
var="$(printf '%s' "${untrusted_text}")"
}}
If you require escapes to be interpreted, interpret them on a per-need basis:
{{CodeSelect|code=
red="$(printf '%b' "\e[31m")" # red=$'\e[31m' # printf -v red '%b' "\e[31m"
nocolor="$(printf '%b' "\e[m")" # nocolor=$'\e[m' # printf -v nocolor '%b' "\e[m"
}}
Escapes that are already interpreted can be printed with %s
without making a difference:
{{CodeSelect|code=
var="$(printf '%s' "${red} ${untrusted_text} ${nocolor}")"
}}
And this is why you should use stprint
when printing to the terminal, as it will sanitize unsafe characters while simply using printf '%s'
is not safe when escapes are already interpreted:
{{CodeSelect|code=
stprint "${red} ${untrusted_text} ${nocolor}"
printf '%s' "${red} ${untrusted_text} ${nocolor}" {{!}} stprint
printf '%s' "${red} ${untrusted_text} ${nocolor}" {{!}} stprint {{!}} less -R
}}
'''Rule of thumb''':
* echo
: Never!
* printf
: Whenever the printed data is not used by a terminal.
** Format %b
: Only for trusted data
** Format %s
: With any data
* stprint
: Whenever the printed data is used by a terminal.
Resources:
* https://github.com/anordal/shellharden/blob/master/how_to_do_things_safely_in_bash.md#echo--printf
* https://unix.stackexchange.com/questions/65803/why-is-printf-better-than-echo
https://pubs.opengroup.org/onlinepubs/9799919799/utilities/echo.html
= Bash Proper Whitespace Handling =
#!/bin/bash ## https://yakking.branchable.com/posts/whitespace-safety/ set -e app_user=user lib_dir="/tmp/test/lib/program with space/something spacy" main_app_dir="/tmp/test/home/user/folder with space/abc" mkdir -p "$lib_dir" mkdir -p "$main_app_dir" declare -a cmd cmd+=("cp") cmd+=("-r") cmd+=("${lib_dir}") cmd+=("${main_app_dir}/") "${cmd[@]}"= Use of End-of-Options Parameter (--) = The end-of-options parameter "
--
" is crucial because otherwise inputs might be mistaken for command options. This might even be a security risk. Here are examples using the `sponge` command:
{{CodeSelect|code=
echo test {{!}} sponge -a testfilename
}}
Result: OK. This works because "testfilename
" doesn't look like an option.
{{CodeSelect|code=
echo test {{!}} sponge -a --testfilename
}}
Result: Fail. The command interprets "--testfilename
" as a series of options:
sponge: invalid option -- '-' sponge: invalid option -- 't' sponge: invalid option -- 'e' ... test{{CodeSelect|code= echo test {{!}} sponge -a -- --testfilename }} Result: OK. The `
--
` signals that "--testfilename
" is a filename, not an option.
Conclusion:
* The "--
" parameter marks the end of command options.
* Use "--
" at the end of a command to prevent misinterpretation.
* This technique is applicable to many Unix/Linux commands, not just sponge
.
= nounset - Check if Variable Exists =
#!/bin/bash set -x set -e set -o nounset ## Enable for testing. #unset HOME if [ -z "${HOME+x}" ]; then echo "Error: HOME is not set." fi echo "$HOME"= Safely Using Find with End-Of-Options = Example: Note: Variable could be different. Could be for example
--/usr
.
{{CodeSelect|code=
folder_name="/usr"
}}
{{CodeSelect|code=
printf '%s' "${folder_name}" {{!}} find -files0-from - -perm /u=s,g=s -print0
}}
Of if safe_echo_nonewline
is available from helper-scripts.
{{CodeSelect|code=
# shellcheck disable=SC1091
source /usr/libexec/helper-scripts/safe_echo.sh
safe_echo_nonewline "${folder_name}" {{!}} find -files0-from - -perm /u=s,g=s -print0
}}
Not using bash's built-in echo
, because it does not support end-of-options ("--
").
Not using /usr/bin/echo
, because it does not support end-of-options ("--
").
{{CodeSelect|code=
## Broken!
## The '-n' option is needed to avoid piping a newline to 'find'.
echo -n -- "${folder_name}" {{!}} find -files0-from - -perm /u=s,g=s -print0
}}
= misc =
base_name="${file_name##*/}" file_extension="${base_name##*.}"= coding style = * use: ** shellcheck **
safe-rm
** scurl
** str_replace
** append-once
* use ${variable}
style
* use shell options
set -o errexit set -o nounset set -o errtrace set -o pipefail* do not use: **
which
, use command -v
instead. This is because which
is an external binary (minor reason) and produces stdout if a binary was found, which can be slightly confusing (major reason).
= See Also =
* https://github.com/anordal/shellharden/blob/master/how_to_do_things_safely_in_bash.md
{{Footer}}
[[Category: Design]]