I am trying to pass a list of files including wildcard files and directories that I want to delete but check for them to see if they exist first before deleted. If they are deleted, notify that the directory was deleted, not each individual file within the directory. Ie if I remove /root/*.tst just say, "I removed *.tst".
#!/bin/bash
touch /boot/{1,2,3,4,5,6,7,8,9,10}.tst
touch /root/{1,2,3,4,5,6,7,8,9,10}.tst
mkdir /root/tmpdir
#setup files and directories we want to delete
file=/boot/*.tst /root/*.tst /root/tmpdir
for i in $file; do
if [[ -d "$file" || -f "$file" ]]; then #do I exist
rm -fr $i
echo removed $i #should tell me that I removed /boot/*.tst or /root/*.tst or /root/tmpdir
else
echo $i does not exist # should tell me if /boot/*.tst or /root/*.tst or /root/tmpdir DNE
fi
done
I can't seem to make any combination of single or double quotes or escaping * make the above do what I want it to do.
Before explaining why your code fails, here is what you should use:
for i in /boot/*.txt /root/*.txt /root/tmpdir; do
# i will be a single file if a pattern expanded, or the literal
# pattern if it does not. You can check using this line:
[[ -f $i ]] || continue
# or use shopt -s nullglob before the loop to cause non-matching
# patterns to be silently ignored
rm -fr "$i"
echo removed $i
done
It appears that would want i
to be set to each of three patterns, which is a little tricky and should probably be avoided, since most of the operators you are using expect single file or directory names, not patterns that match multiple names.
The attempt you show
file=/boot/*.tst /root/*.tst /root/tmpdir
would expand /root/*.tst
and try to use the first name in the expansion as a command name, executed in an environment where the variable file
had the literal value /boot/*.tst
. To include all the patterns in the string, you would need to escape the spaces between them, with either
file=/boot/*.tst\ /root/*.tst\ /root/tmpdir
or more naturally
file="/boot/*.tst /root/*.tst /root/tmpdir"
Either way, the patterns are not yet expanded; the literal *
is stored in the value of file
. You would then expand it using
for i in $file # no quotes!
and after $file
expands to its literal value, the stored patterns would be expanded into the set of matching file names. However, this loop would only work for file names that didn't contain whitespace; a single file named foo bar
would be seen as two separate values to assign to i
, namely foo
and bar
. The correct way to deal with such file names in bash
is to use an array:
files=( /boot/*.tst /root/*.tst /root/tmpdir )
# Quote are necessary this time to protect space-containing filenames
# Unlike regular parameter assignment, the patterns were expanded to the matching
# set of file names first, then the resulting list of files was assigned to the array,
# one file name per element.
for i in "${files[@]}"
You can replace
file=/boot/*.tst /root/*.tst /root/tmpdir
by
printf -v file "%s " /boot/*.tst /root/*.tst /root/tmpdir
The shell expands globs automatically. If you want to be able to print the literal globs in an error message then you'll need to quote them.
rmglob() {
local glob
for glob in "$@"; do
local matched=false
for path in $glob; do
[[ -e $path ]] && rm -rf "$path" && matched=true
done
$matched && echo "removed $glob" || echo "$glob does not exist" >&2
done
}
rmglob '/boot/*.tst' '/root/*.tst' '/root/tmpdir'
Notice the careful use of quoting. The arguments to deleteGlobs
are quoted. The $glob
variable inside the function is not quoted ( for path in $glob
) which triggers shell expansion at that point.
Many thanks to everyones' posts including John Kugelman.
This is the code I finally went with that provided two types of deleting. The first is a bit more forceful deleting everything. The second preserved directory structures just removing the files. As per above, note that whitespace in file names is not handled by this method.
rmfunc() {
local glob
for glob in "$@"; do
local matched=false
local checked=true
for path in $glob; do
$checked && echo -e "\nAttempting to clean $glob" && checked=false
[[ -e $path ]] && rm -fr "$path" && matched=true
done
$matched && echo -e "\n\e[1;33m[\e[0m\e[1;32mPASS\e[1;33m]\e[0m Cleaned $glob" || echo -e "\n\e[1;33m[\e[0m\e[1;31mERROR\e[1;33m]\e[0m Can't find $glob (non fatal)."
done
}
# Type 2 removal
xargfunc() {
local glob
for glob in "$@"; do
local matched=false
local checked=true
for path in $glob; do
$checked && echo -e "\nAttempting to clean $glob" && checked=false
[[ -n $(find $path -type f) ]] && find $path -type f | xargs rm -f && matched=true
done
$matched && echo -e "\n\e[1;33m[\e[0m\e[1;32mPASS\e[1;33m]\e[0m Cleaned $glob" || echo -e "\n\e[1;33m[\e[0m\e[1;31mERROR\e[1;33m]\e[0m Can't find $glob (non fatal)."
fi
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.