Consider suggesting `&> afile` instead of `> afile 2>&1`
For new checks and feature suggestions
- [x] https://www.shellcheck.net/ (i.e. the latest commit) currently gives no useful warnings about this
- [x] I searched through https://github.com/koalaman/shellcheck/issues and didn't find anything related
Consider suggesting &> afile instead of > afile 2>&1 to redirect both stdout and stderr to the same file.
Here's a snippet or screenshot that shows the problem:
#!/usr/bin/env bash
function foo()
{
echo -n "$1 " >&2 # send to stderr
echo "$2" # this goes to stdout
}
foo error out &>bla # here both strings to to file bla
Here's what shellcheck currently says:
Nothing
Here's what I wanted or expected to see:
This might be a bit obscure (in the sense few people know about), but helps me a lot to not screw up the order of the redirects.
- info: consider using
&> afileinstead of> afile 2>&1
Works on Bash and sh, did not test on other shells.
That's not valid in POSIX sh, and in fact, we already have a warning to make the exact opposite change (SC3020).
See also https://unix.stackexchange.com/a/590707/217726:
&>is a synonym of>&followed by a file name in bash (and zsh). It has the advantage of working even if the file name is a sequence of digits or-(for examplefoo >&"$a"is equivalent tofoo 1>&3 2>&1if the value ofais the digit3, but tofoo >myfile 2>&1if the value ofaismyfile). Its downside is that it is incompatible with POSIX. In a POSIX-compliant shell,echo foo &>baris parsed as the commandecho foo, the&operator, and the command>bar: a background process that printsfoo, and a foreground process that creates or empties the filebar.
And https://unix.stackexchange.com/a/159514/217726:
The
&>variant originates inbash, while the>&variant comes from csh (decades earlier). They both conflict with other POSIX shell operators and should not be used in portableshscripts.
&> is a badly broken csh-ism. As @josephcsible suggests, it shouldn't be encouraged.
(ins)ormaaj 69 (1425001) 0 / $ ( eval -- set -x \; "${ tmux saveb - | tee /proc/self/fd/2; }" )
function runtest {
unset -f "${FUNCNAME[0]}"
local -; set +m
typeset -ia fd
typeset -n fdn='fd[${#fd[@]}]'
{
execlineb "/proc/self/fd/${fd[-4]}" "${fd[-5]}" bash "${bob[@]}" -p "/proc/self/fd/${fd[-3]}"
} {fdn}<<-\EOF0 {fdn}<<-\EOF1 {fdn}<<-\EOF2 {fdn}<&0 {fdn}<&1 < <(typeset -p fd)
echo foo >/dev/null &>"/proc/self/fd/${1}" echo bar
EOF0
fdreserve 1
importas x 1
shift
multisubstitute {
importas -u f FD0
elgetpositionals
}
emptyenv -P
creatememfd $f "" # https://github.com/emanuele6/emanutils
foreground { cp -L -- /proc/self/fd/${x} /proc/self/fd/${f} }
fdclose $x
export BASH_COMPAT 51
$@ $f
EOF1
function ret {
return ${1:+"$1"}
}
function _main {
typeset sh
{
sh=$(pkgconf --var=loadablesdir bash) &&
enable -f "${sh}/fdflags" fdflags &&
source /proc/self/fd/0 &&
[[ -v fd && $1 == +([[:digit:]]) ]]
} || return
fd+=([-5]=$1)
exec <&"${fd[-2]}"- {fd[-4]}<&-
fdflags -s +cloexec "${fd[-3]}"
set -- bash zsh mksh toybox bb ksh ksh93v loksh dash yash bosh jsh
printf '\ntest: %s\n\n' "$(<"/proc/self/fd/${fd[-5]}")"
for sh; do
printf '%-7s ' "${sh}:"
case $sh in
toybox) ret 1;;
zsh) ret 2
esac
(
exec -a ${PPID[!($?!=1)]+"$sh"}${PPID[!($?==1)]+'sh'} -- \
"$sh" ${PPID[$?!=2]+'--emulate'} ${PPID[$?!=2]+'ksh'} "/proc/self/fd/${fd[-5]}" "${fd[-1]}"
)
done
}
_main "$@"
EOF2
{ BASH_XTRACEFD=2 set +x; } 2>/dev/null
typeset -i x
for x in "${!fd[@]}"; do
exec {fd[x]}<&-
done
}
runtest
++ runtest
++ unset -f runtest
++ local -
++ set +m
++ typeset -ia fd
++ typeset -n 'fdn=fd[${#fd[@]}]'
++ execlineb /proc/self/fd/11 10 bash -o vi +o histexpand -O lastpipe -O extglob -O expand_aliases +O array_expand_once +O assoc_expand_once -p /proc/self/fd/12
+++ typeset -p fd
test: echo foo >/dev/null &>"/proc/self/fd/${1}" echo bar
bash: foo echo bar
zsh: foo echo bar
mksh: foo echo bar
toybox: foo echo bar
bb: foo echo bar
ksh: foo echo bar
ksh93v: bar
loksh: bar
dash: bar
yash: bar
bosh: bar
jsh: bar