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',