echo \\* - bash backslash escape behavior, is it evaluated backwards?
Solution 1
If you don't have a file in the current directory whose name starts with a backslash, this is expected behaviour. Bash expands *
to match any existing file names, but:
If the pattern does not match any existing filenames or pathnames, the pattern string shall be left unchanged.
Because there was no filename starting with \
, the pattern was left as-is and echo
is given the argument \*
. This behaviour is often confusing, and some other shells, such as zsh
, do not have it. You can change it in Bash using shopt -o failglob
, which will then give an error as zsh
does and help you diagnose the problem instead of misbehaving.
The *
and ?
pattern characters can appear anywhere in the word, and characters before and after are matched literally. That is why echo \\*
and echo \\ *
give such different output: the first matches anything that starts with \
(and fails) and the second outputs a \
, and then all filenames.
The most straightforward way of getting the output you want safely is probably to use printf:
printf '\\'; printf "%s " *
echo *
is unsafe in the case of unusual filenames with -
or \
in them in any case.
Solution 2
1: glob
When I do
echo \*
I get*
This seems right, as * is escaped and taken literally.
Yes, with an escape \
(quote), an otherwise special character becomes regular and therefore is printed back as it is, a *
.
But I can't understand that, when I do
echo \\*
I get\*
Because you need to think about "an string" to expand (via globbing). The string is \\*
, which, because it is un-quoted and it has an special character (the backslash) the first backslash quotes the second and becomes one backslash followed by an asterisk (\*
). The string: backslash (\
) followed by anything (*
) should match literally that: a file name that start with a backslash and followed by more characters. If there are no files like \a
, \one
or \file
the globbing of \*
fails, that is, it can not expand to a "list of files". If some options in bash (failglob or nullglob) are not set, the string \*
remains and is printed as it was. You get \*
back.
2: concatenation
I was expecting:
$ echo \\*
\file1 file2 file3
Above we clarified why \\*
ends up being the string \*
, that is: one string. There is no concatenation of two strings as in:
$ a='\'
$ b='file1 file2 file3'
$ echo "$a$b"
/file1 file2 file3
That is:you need two strings to get them concatenated. One string could be a variable of the literal \
, the other could be the list of files in the present directory as an string. One way to accomplish that is:
$ set -- * # get the list of files as arguments.
$ echo '\'"$*" # or "\\$*". Convert arguments to an string $*
# and concatenate '\' to it.
\file1 file2 file3
Solution 3
I believe that all the other answers to the second half of the question (“What command should I use?”) are wrong, if only technically or in edge cases. I believe that this addresses the problems in the other answers:
(set -- *; echo "\\$*")
and (FWIW) all the writing is being done in a single command
(unlike printf '\\'; printf "%s " *; echo
).
- Use a subshell to avoid setting / changing the positional parameters in an outer scope / context.
- Naively,
set *
sets the positional parameters ($1
,$2
,$3
, etc…) to the names of the files in the current directory. (Filenames beginning with.
(dot) are included, or not, based on whether thedotglob
option is set.) -
set *
can fail if there is a file whose name begins with-
(dash) (and it is the first file in the*
list).set -- *
handles this;--
says “everything after this is an argument and not an option / flag.” -
$*
is the list of positional parameters ($1 $2 $3 …
),with the parameters separated by space(s). The\\
causes\
to be output at the beginning of the line, just as in theecho \\ *
(example command in the question), but it doesn’t interfere with the expansion of the subsequent$*
. -
$*
is the list of positional parameters ($1 $2 $3 …
), with the parameters separated by the first character of$IFS
. If there’s a possibility that the first character of$IFS
might not be space, do
(set -- *; IFS=" "; echo "\\$*")
Slightly more verbose options include
(set -- *; printf "%s\n" "\\$*")
and
(set -- *; printf "\\%s\n" "$*")
These are safer in an environment
where echo
might interpret backslashes in the argument strings
even without an -e
option.
Again, add IFS=" ";
if there’s a possibility
that the first character of $IFS
might not be space.
Solution 4
Try this:
printf "\\%s" "$(echo *)"
Explanation:
-
printf
takes a format argument and zero or more arguments that are substituted into the format string -
\\
in the format has the same meaning as inecho \\
-
%s
means take the next argument and substitute it into the result -
"$(echo *)"
means: executeecho *
, and put the result into a single argument toprintf
; it has to be put into a single argument because of howprintf
works
Solution 5
No, bash
escape character preserves the literal value of the next character that follows, from left to right, so \\*
give you pattern \*
.
This pattern is performed Filename Expansion, with the Pattern Matching rules.
So \*
is interpret as all files starting with \
and follows by anything. In your case it matched nothing and bash
left the pattern \*
unchanged to echo
.
Related videos on Youtube
Weishi Z
Updated on September 18, 2022Comments
-
Weishi Z over 1 year
So in bash, When I do
echo \* *
This seems right, as * is escaped and taken literally.
But I can't understand that, when I do
echo \\* \*
I thought the first backslash escaped the second one thus two backslash "\\" will give me one "\" in literal. and * followed carrying its special meaning. I was expecting:
echo \\* \file1 file2 file3
ANSWER SUMMARY: Since \ is taken literally,
echo \*
will behave just asecho a*
, which will find any file that starts with literal "a".Follow up question, If I want to print out exactly like
\file1 file2 file3
What command should I use? e.g. like the following but I want no space
echo \\ * \ file1 file2 file3
-
cuonglm about 9 yearsIs there any shells except
zsh
print the error message if matching fail by default? -
Michael Homer about 9 years
fish
, at least. Maybe others, although I don't recall off-hand. -
Scott - Слава Україні over 5 yearsI don’t quite understand what you’re doing (i.e., what you’re accomplishing; what problem you’re trying to solve). And, maybe, if I understood that, I’d understand how this relates to the question — but I don’t. Please do not respond in comments; edit your answer to make it clearer and more complete. P.S. Why do you say
var=`echo value`
instead ofvar=value
? -
Scott - Слава Україні over 5 years(1) The question says “like …
echo \\ *
… but I want no space”. Your answer fails because it doesn’t include the newline at the end. (Of course you can fix that by adding; echo
or; printf '\n'
.) (2) Why do you use single quotes for one string and double quotes for the other? -
Scott - Слава Україні over 5 years(1) The question says “like …
echo \\ *
… but I want no space”. Your answer fails because it doesn’t include the newline at the end. … … … … … … (Of course you can fix that by sayingprintf "\\%s\n" "$(echo *)"
.) … … … (2) You could change\\
to\
in your answer (printf "\%s" "$(echo *)"
), and it would work as well. … … … … … … … … … (3) Also, as far as I can tell,echo "\\$(echo *)"
would work as well. … (Cont’d) -
Scott - Слава Україні over 5 years(Cont’d) … (4) The real problem with this answer is that it depends on the mercurial behavior of
echo
. If there is a file named-e
or-n
(and it is the first file in the*
list), it will disappear. -
Scott - Слава Україні over 5 years(1) The question says “like …
echo \\ *
… but I want no space”. Your answer fails because it doesn’t include the newline at the end. (It seems especially odd that you missed this, since you included a trailing newline in the last line of your answer, where you’re answering a different question.) (2) Since your answer usesset
, it should be put into a subshell, to avoid setting / changing the positional parameters in an outer scope / context. … (Cont’d) -
Scott - Слава Україні over 5 years(Cont’d) … (3) The question talks about
echo … *
, and indicates that the current directory containsfile1
,file2
, andfile3
. (Arguably, we should treat these as placeholders rather than examples.) Usingfile*
in your answer seems inappropriate. (4) So, any correct answer should include a plain*
. Making the appropriate change to your answer, we getset *
, which can fail if there is a file whose name begins with-
(dash). … (Cont’d) -
Scott - Слава Україні over 5 years(Cont’d) … (5) Why use
$@
and a literal space in the format string when you could use$*
and let the shell interpolate space(s) for you? (OK, yes, I know aboutIFS
.) (6) People less clever and tricky than you might not realize that you are using a literal space in the format string because it isn’t quoted. A good answer would make a detail like that explicit, and explain it. -
Wildcard over 4 yearsThe
printf
command given will print a trailing space, and will not print a trailing newline. It's sort of an academic question at that point anyway, but it's worth mentioning those caveats.