Para a terceira versão, você deseja "$*" não "$@" .
Explicação
Para ilustrar, vamos definir alguns argumentos posicionais:
$ set -- arg1 arg2 arg3
Agora, vamos lê-los com sua formulação echo :
$ printf "%s\n" "$(echo $@)"
arg1 arg2 arg3
Vamos ver o que o "$@" faz com eles:
$ printf "%s\n" "$@"
arg1
arg2
arg3
A diferença é que "$@" se expande para três palavras, enquanto "$(echo $@)" se expande para uma única string. Se você quiser um comportamento como "$(echo $@)" , use "$*" :
$ printf "%s\n" "$*"
arg1 arg2 arg3
Documentação
De man bash :
"$*" produz uma única string:
"$*" is equivalent to "$1c$2c...", where c is the first character of the value of the IFS variable.
Por contraste, man bash afirma que $@ produz uma série de palavras:
"$@" is equivalent to "$1" "$2" ...
Usando o eval
Observe:
$ echo 'printf "%s\n" "' "$@" '"'
printf "%s\n" " arg1 arg2 arg3 "
Quando o bash avalia o comando echo , "$@" expande para separar palavras. echo , no entanto, pega essas palavras separadas e as combina em uma string. Nós vimos isso antes quando olhamos para "$(echo $@)" . eval funciona de maneira semelhante. Faz uma string fora de todos os seus argumentos e avalia essa string. Assim:
$ eval 'printf "%s\n" "' "$@" '"'
arg1 arg2 arg3
Isso também é documentado por man bash :
eval [arg ...]
The args are read and concatenated together into a single command. This command is then read and executed by the shell, ...