Is $(printf '%q ' "${@:1}")
equivalent to "${*}"
?
If they are, then, doing $(printf '%q ' "${@:2}")
(note the 2 instead of 1 as before) is not possible with pure bash $*
?
Related questions:
No, it is not equivalent, because words are splitted. Ex. the following code:
check_args() {
echo "\$#=$#"
printf "%s\n" "$@";
}
# setting arguments
set -- "space notspace" "newline"$'\n'"newline"
echo '1: ---------------- "$*"'
check_args "$*"
echo '2: ---------------- $(printf '\''%q '\'' "${@:1}")'
check_args $(printf '%q ' "${@:1}")
echo '3: ---------------- "$(printf '\''%q '\'' "${@:1}")"'
check_args "$(printf '%q ' "${@:1}")"
echo '4: ---------------- IFS=@ and "$*"'
( IFS=@; check_args "$*"; )
echo "5: ---------------- duplicating quoted"
check_args "$(printf '%s'"${IFS:0:1}" "${@:1}" | sed 's/'"${IFS:0:1}"'$//')"
echo "6: ---------------- duplicating quoted IFS=@"
( IFS=@; check_args "$(printf '%s'"${IFS:0:1}" "${@:1}" | sed 's/'"${IFS:0:1}"'$//')"; )
echo "7: ---------------- duplicating eval unquoted"
eval check_args $(printf '%q"'"${IFS:0:1}"'"' "${@:1}" | sed 's/'"${IFS:0:1}"'$//')
echo "8: ---------------- duplicating eval unquoted IFS=@"
( eval check_args $(IFS=@ ; printf '%q"'"${IFS:0:1}"'"' "${@:1}" | sed 's/"'"${IFS:0:1}"'"$//'); )
will output:
1: ---------------- "$*"
$#=1
space notspace newline
newline
2: ---------------- $(printf '%q ' "${@:1}")
$#=3
space\
notspace
$'newline\nnewline'
3: ---------------- "$(printf '%q ' "${@:1}")"
$#=1
space\ notspace $'newline\nnewline'
4: ---------------- IFS=@ and "$*"
$#=1
space notspace@newline
newline
5: ---------------- duplicating quoted
$#=1
space notspace newline
newline
6: ---------------- duplicating quoted IFS=@
$#=1
space notspace@newline
newline
7: ---------------- duplicating eval unquoted
$#=1
space notspace newline
newline
8: ---------------- duplicating eval unquoted IFS=@
$#=1
space notspace@newline
newline
tested on repl.
The "$*"
outputs the arguments delimetered by IFS. So, shown in test 4
, if delimeter is not unset or set to space, then the output of $*
will be delimetered by IFS, @
in this example.
Also when IFS is unset or set to space, the output of $*
does not include a terminating space, while printf '%q '
will always print a trailing space on the end of the string.
The output of $(printf '%q ' "${@:1}")
is still splitted on space. So the test case 2
receives 3 arguments, because the space notspace
string is separated by space and splitted to two arguments. When enclosing the printf
inside "
will not help - printf
substitutes ex. newlines for \n
characters.
Cases 5
, 6
, 7
, 8
are my tries to replicate the behavior of "$*"
using printf. It can be seen with cases 7
and 8
I used eval
, with cases 5
and 6
I quoted the command substitution. The output of cases ( 5
and 6
) and ( 7
and 8
) should match the output of cases 1
and 4
respectively.
For duplicating the behavior of "$*"
special care needs to be taken for IFS
to properly delimeter the strings. I used sed 's/'"${IFS:0:1}"'$//'
to remove the trailing IFS separator from the printf
output. The 5
and 6
cases are unquoted $(printf ...)
tries, with 6
using IFS=@
to show the separating works. The 7
and 8
cases use eval with special handling on the IFS
, cause the IFS
character itself needs to be enclosed with quotes, so the shell will not split on it again, that's why printf '%q"'"${IFS:0:1}"'"'
.
doing $(printf '%q ' "${@:2}") (note the 2 instead of 1 as before) is not possible with pure bash $*?
You probably could just shift the arguments inside the substitution $(shift; printf "%s\n" "$*")
, but as shown above, they are not equivalent anyway.