I was trying to remove a file within a repository which contained a "$" symbol, so I used:
rm *$*
But this deleted all my files in the directory and attempted to delete all subdirectories as well.
Can someone explain why this command did not just remove files containing a $?
*$* first undergoes parameter expansion, with $* expanding to a string consisting of all the current positional parameters. If there are none, the result is *, which then undergoes pathname expansion.
The correct command would have been something like rm *"$"* or rm *\$*, with the $ escaped to prevent any parameter expansion from taking place before pathname expansion.
This question already has an answer here:
In bash, how do I expand a wildcard while it's inside double quotes?
(1 answer)
Closed 2 years ago.
I’m trying to execute a shell command that deletes the content of the folder. This was executed on macOS Catalina.
This command works:
rm -r /path/folder/*
But this won’t work:
rm -r “/path/folder/*”
I wonder what is the effect of those quotes. The path changes because it is executed from my program. I added quotes in the path to make up for spaces in the path if it exists. Then I tested it in actual command line to check and it behaves like that.
The quotes are to block-escape paths containing space characters.
In case of an asterisk as a wildcard you have to keep the asterisk out of the block
rm -r "/path/my great folder"/*
An alternative is to escape each space character with a backslash.
rm -r /path/my\ great\ folder/*
in bash, we can say
dnf install <some-pkg>-*
or
rm -rf *.jpg
but when I'm using zsh, it doesn't do anything with the star (*) character!
how can I do those commands on zsh?!
These are two different uses of globs. With dnf, you expect dnf itself to expand the pattern against the available packages. With rm, you are expecting the shell to expand the pattern against the files in the current directory and passing the resulting names to rm.
In bash, the default behavior is for a pattern that doesn't match anything to be treated as a literal string. That's why the dnf example works in bash: when there are no local files matching <some-pkg>-*, the literal string is passed to zsh.
The solution is to quote strings that you intended to be treated literally, instead of relying on your shell's treatment of unmatched patterns. The following will work as intended in both shells:
dnf install "<some-pkg>.*"
rm -rf *.jpg
In both shells, you can change how unmatched patterns are treated. To make zsh act like the bash default, use
setopt NO_NOMATCH
To make bash behave like the zsh default, you could use
shopt -s failglob
Probably, the globbing is disabled on your zsh. Enable it using the below command in zsh:
setopt GLOB
I'm writing a script to remove some files, but I don't understand how asterisk expansions work. These are my attempts to solve my problem:
rm "$path"*.txt
rm "$path"/*.txt
rm "$path"{*}.txt
rm "$path"'*'
rm "/folder/folder\ with\ spaces/*.txt"
I also tried replacing double quotes (") with single quotes(') and backticks (`). After every script computation, I get an error because the * is not substitute. So now I have two questions:
Why is the asterisk not expanded?
What's the difference between the different quoting character (` " ' ...) ?
In single quoted nothing interesting happns. Not even $-variable expansion. Some of those you've tried should work (some depending on the variable content). And, really, * is most likely to be not expanded if there're no matches. Are you sure you got your names right?
I want to do something like this in a bash script. I'm using bash 4.1.10.
# rm -rf /some/path/{folder1,folder2,folder3}
Works nicely (and as expected) from the shell itself. It deletes the 3 desired folders leaving all others untouched.
When I put it into script something unwanted happens. For example, my script:
#!/bin/bash
set -x
VAR="folder1,folder2,folder3"
rm -rf /some/path/{$VAR}
When I execute this script, the folders are not deleted.
I think this is due to the fact that some unwanted quoting is occurring. Output from the script using #!/bin/bash -x:
rm -rf '/some/path/{folder1,folder2,folder3}'
which of course cannot succeed due to the ' marks.
How can I get this working within my script?
According to the man page:
The order of expansions is: brace expansion, tilde expansion, parameter, variable and arithmetic expansion and command substitution (done in a left-to-right fashion), word splitting, and pathname expansion.
So to get around this, add another level of expansion:
eval "rm -rf /some/path/{$VAR}"
Since you're writing a script, there's no reason to write hard-to-maintain code using eval tricks
VAR="f1,f2,f3"
IFS=,
set -- $VAR
for f; do
rm -r "/path/to/$f"
done
or
VAR=( f1 f2 f3 )
for f in "${VAR[#]}"; do
rm -r "/path/to/$f"
done
No, it's due to the fact that brace expansion happens before parameter expansion. Find another way of doing this, such as with xargs.
xargs -d , -I {} rm -rf /some/path/{} <<< "$VAR"
If your code can be rearranged, you can use echo and command substitution in bash.
Something like this:
#!/bin/bash
set -x
VAR=`echo /some/path/{folder1,folder2,folder3}`
rm -rf $VAR
You need to enable braceexpand flag:
#!/bin/bash
set -B
for i in /some/path/{folder1,folder2,folder3}
do
rm -rf "$i"
done
The problem is not that in script mode some unwanted quoting is happening but that you put the folder names into a variable and the variable content is expanded after the brace expansion is done.
If you really want to do it like this you have to use eval:
eval "rm -rf /some/path/{$VAR}"
#!/bin/bash
set -x
VAR="folder1,folder2,folder3"
eval "rm -rf /some/path/{$VAR}"
Edit The remainder is just for info. I try to be informative, but not wordy :_)
Recent bashes have the globstar option. This might perhaps come in handy in the future
shopt -s globstar
rm -rfvi some/**/folder?
Another trick you can use (instead of the dangerous eval) is just plain echo inside a subshell. This works, for instance:
paths=`echo /some/path/{folder1,folder2,folder3}`
echo rm -rf $paths
outputs:
rm -rf /some/path/folder1 /some/path/folder2 /some/path/folder3
as desired. (Remove the "echo" in the second line, to make it actually do the rm.)
The crucial point is that bash does brace expansion before parameter expansion, so you never want to put a comma-separated list (surrounded by braces or not) into a variable -- if you do, then you'll have to resort to eval. You can however put a list of space-separated strings into a variable, by having the brace expansion happen in a subshell before assignment.
replace {$VAR} by ${VAR} :-)