Given these six files:
$ touch 'sec*et' 'sec\*et' 'sec\et' secet secret 'sec\ xxx et'
Why backslash in unquoted variable for glob expansion matches only the sec\*et
file?
$ v="sec\*et" ; ls $v
'sec\*et'
$ v='sec\*et' ; ls $v
'sec\*et'
Related to this SO answer, the POSIX definition:
The < backslash > shall retain its special meaning as an escape character ... only when followed by one of the following characters when considered special:
$
`
"
\
<newline>
and the Bash manual:
The backslash retains its special meaning only when followed by one of the following characters: ‘
$
’, ‘`
’, ‘"
’, ‘\
’, or newline. Within double quotes, backslashes that are followed by one of these characters are removed. Backslashes preceding characters without a special meaning are left unmodified.
I understand that the backslash (before an asterisk) in the variable is a literal backslash:
$ v='sec\*et' ; printf '%s' "$v" | hexdump -C
00000000 73 65 63 5c 2a 65 74 |sec\*et|
00000007
But I do not understand why the wildcard character *
loses it special meaning after a literal backslash.
Three things I understand:
(A) An asterisk *
in an unquoted variable has its special meaning in glob expansion. They are equivalent:
$ v='sec*et' ; ls $v
'sec*et' 'sec\*et' 'sec\et' secet secret 'sec\ xxx et'
$ ls sec*et
'sec*et' 'sec\*et' 'sec\et' secet secret 'sec\ xxx et'
(B) An asterisk *
loses its special meaning after a backslash:
$ ls sec\*et
'sec*et'
(C) A literal backslash cannot make the asterisk *
loses its special meaning:
$ v='sec\\*et' ; ls $v
'sec\*et' 'sec\et' 'sec\ xxx et'
$ ls sec\\*et
'sec\*et' 'sec\et' 'sec\ xxx et'
This is what I do not understand:
However it is weird that, the asterisk loses its special meaning, but the backslash is not discarded, in this case:
$ v='sec\*et' ; ls $v
'sec\*et'
Somehow it is equivalent to a literal backslash followed by a literal asterisk:
$ ls sec\\\*et
'sec\*et'
But why? Consider:
If special characters in quotes become literal, (A) does not hold.
If the asterisk becomes literal because of following a backslash, why the backslash is not discarded, and matches the file
sec*et
, like in (B)?
In application, other than using Bracket Expression [*]
, how to define a string variable that matches a literal asterisk when used in glob expansion?
$ v='sec < what what what > et' ; ls $v
'sec*et'
Word Expansions
while variable undergoesParameter Expansion
. If a variable is unquoted, it then undergoesPathname Expansion
. However I am still not sure if it will perform Quote Removal. It does in case (C). But it does not in the questioned case. – midnite Jan 17 '24 at 13:17[*]
to match a literal*
portably when in a glob that is a result of an unquoted expansion. – Stéphane Chazelas Jan 17 '24 at 13:23sec\*et
. And when the glob is expanded later, it doesn't matter how the variable got the value it had. (You could have filled it with e.g.read
instead of a regular assignment.) – ilkkachu Jan 17 '24 at 18:29