How to test if a string starts with a prefix from a list of prefixes in a shell
I have a requiremen开发者_开发百科t in a shell script. I get this location information from a text file; it is always valid.
/opt/sasuapps/senny/publish/gbl/SANDHYA/drop1
I need to check if the directory is empty or not which I have done. If the directory is not empty, I need to delete the files and directory under that location.
As a part of security check, I would like to check if the drop location got from the file (/opt/sasuapps/senny/publish/gbl/SANDHYA/drop1) starts with any of the below.
/mnt/senny_publish/gbl
/mnt/senny/publish/gbl
/opt/sasuapps/senny/publish/gbl
If yes then only go ahead and delete; else don't do anything.
How can I compare the location given with those fixed strings?
This will work in bash and any other Posix-style shell, i.e., it's OK for systems where /bin/sh is not bash.
check () {
[ "x$1" = x ] && return 1
for pf in /mnt/senny_publish/gbl \
/mnt/senny/publish/gbl \
/opt/sasuapps/senny/publish/gbl; do
suf="${1#$pf}"
[ x"$pf$suf" = x"$1" ] && return 0
done
return 1
}
testcheck () {
echo -n "$1" :
if check "$1"; then
echo OK
else
echo BAD
fi
}
testcheck /how/now
testcheck /not/this
testcheck /mnt/senny_publish/gbl
testcheck /mnt/senny/publish/gbl
testcheck /opt/sasuapps/senny/publish/gbl
testcheck /mnt/senny_publish/gbl/a/b
testcheck /mnt/senny/publish/gbl/a/b
testcheck /opt/sasuapps/senny/publish/gbl/a/b
So...
/how/now :BAD
/not/this :BAD
/mnt/senny_publish/gbl :OK
/mnt/senny/publish/gbl :OK
/opt/sasuapps/senny/publish/gbl :OK
/mnt/senny_publish/gbl/a/b :OK
/mnt/senny/publish/gbl/a/b :OK
/opt/sasuapps/senny/publish/gbl/a/b :OK
By avoiding grep
and other external programs, we keep execution entirely in the shell, avoiding fork's and exec's, and which may also give additional protection against XSS attacks. Still, it would be a good idea to filter metachars early.
Assuming you are using bash for your shell script:
if [ -n "$(echo $LOCATION|grep -lE '/mnt/senny_publish/gbl|/mnt/senny/publish/gbl|/opt/sasuapps/senny/publish/gbl')" ]
then
# Contains one of these paths
else
# Does not contain one of these paths
fi
If you have a longer list of paths to look through, you could dump them to a tempfile, one per line, and use grep -lEf tempFileWithPaths.txt
Using case
is a lot cleaner:
case $d in
/mnt/foo/gbl/*|/mnt/bar/publish/gbl/*|/opt/sasu/bar/publish/gbl/*)
rm -fr "$d"
esac
..for everyday use. This is POSIX-compliant and quick.
If you want to make it into a function, you could do:
# prefixed_by "$locn" "$safe_pfx" '/dir/list'
prefixed_by() {
[ -n "$1" ] || return
local i f=$1; shift
for i; do
i=${i%/}
case $f in
"$i/"*) return 0
esac
done
return 1
}
So, if you know the list never has spaces or wildcard characters in it (ie: it's set directly in your script with a string literal) you could do:
readonly safe_list='/mnt/foo/gbl /mnt/bar/publish/gbl /opt/sasu/bar/publish/gbl'
..in initialisation, and then later:
prefixed_by "$d" $safe_list && rm -fr "$d"
I've used local
in the function, which isn't specified in POSIX, though most modern shells have it: you can change that to fit your circumstance, as the rest is all POSIX-compliant.
In BASH, you don't have to use case
in the for
loop: you can just use [[
which pattern-matches, ie:
[[ $f = "$i"/* ]] && return
It also doesn't word-split nor perform any filename expansion. Quoting the "$i"
is needed, however to ensure any wildcard characters like *
or ?
are not used as wildcards.
use sed command
精彩评论