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:
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.
Using @Kamil Cuk answer as base, I built this new testing code for illustration, also available on repl:
#!/bin/bash check_args() { echo "\$#=$#" local counter=0 for var in "$@" do counter=$((counter+1)); printf "$counter. '$var', "; done printf "\\n\\n" } # setting arguments set -- "space notspace" "lastargument"; counter=1 echo $counter': ---------------- "$*"'; counter=$((counter+1)) check_args "$*" echo $counter': ---------------- $*'; counter=$((counter+1)) check_args $* echo $counter': ---------------- "$@"'; counter=$((counter+1)) check_args "$@" echo $counter': ---------------- $@'; counter=$((counter+1)) check_args $@ echo $counter': ---------------- $(printf '\''%q '\'' "${@:1}")'; counter=$((counter+1)) check_args $(printf '%q ' "${@:1}") echo $counter': ---------------- "$(printf '\''%q '\'' "${@:1}")"'; counter=$((counter+1)) check_args "$(printf '%q ' "${@:1}")" echo $counter': ---------------- IFS=@ and "$*"'; counter=$((counter+1)) ( IFS=@; check_args "$*"; ) echo "$counter: ---------------- duplicating quoted"; counter=$((counter+1)) check_args "$(printf '%s'"${IFS:0:1}" "${@:1}" | sed 's/'"${IFS:0:1}"'$//')" echo "$counter: ---------------- duplicating quoted IFS=@"; counter=$((counter+1)) ( IFS=@; check_args "$(printf '%s'"${IFS:0:1}" "${@:1}" | sed 's/'"${IFS:0:1}"'$//')"; ) echo "$counter: ---------------- duplicating eval unquoted"; counter=$((counter+1)) eval check_args $(printf '%q"'"${IFS:0:1}"'"' "${@:1}" | sed 's/'"${IFS:0:1}"'$//') echo "$counter: ---------------- duplicating eval unquoted IFS=@"; counter=$((counter+1)) ( eval check_args $(IFS=@ ; printf '%q"'"${IFS:0:1}"'"' "${@:1}" | sed 's/"'"${IFS:0:1}"'"$//'); ) -->
GNU bash, version 4.4.12(1)-release (x86_64-pc-linux-gnu) 1: ---------------- "$*" $#=1 1. 'space notspace lastargument', 2: ---------------- $* $#=3 1. 'space', 2. 'notspace', 3. 'lastargument', 3: ---------------- "$@" $#=2 1. 'space notspace', 2. 'lastargument', 4: ---------------- $@ $#=3 1. 'space', 2. 'notspace', 3. 'lastargument', 5: ---------------- $(printf '%q ' "${@:1}") $#=3 1. 'space', 2. 'notspace', 3. 'lastargument', 6: ---------------- "$(printf '%q ' "${@:1}")" $#=1 1. 'space\ notspace lastargument ', 7: ---------------- IFS=@ and "$*" $#=1 1. 'space notspace@lastargument', 8: ---------------- duplicating quoted $#=1 1. 'space notspace lastargument', 9: ---------------- duplicating quoted IFS=@ $#=1 1. 'space notspace@lastargument', 10: ---------------- duplicating eval unquoted $#=1 1. 'space notspace lastargument ', 11: ---------------- duplicating eval unquoted IFS=@ $#=1 1. 'space notspace@lastargument',
"${*}"(more usually written"$*") yields a single string. POSIX does not specify the%qconversion specification for either theprintfcommand or theprintf()function. You have to refer to the documentation for your system to know what that's going to do.