I want to write a simple script to detect a file created by Windows virus. Usually it creates an .exe file, with the same name, as the directory it drops.
Here is the script. It only works, if the path name doesn't contain \n. Can someone help me fix this script, please!
#!/bin/bash
if [ $# == 0 ]; then
echo ""
echo "==== Give me a directory to begin with! ===="
echo ""
exit
fi
for f in `find $1 -name '*.exe'` ; do
filename=`basename "$f" .exe`
dir_name=`dirname "$f"`
current_dir_name=`basename "$dir_name"`
if [ $filename == $current_dir_name ]; then
rm -f "$f" # It can't remove files
# where the path contains spaces or \n ??!!
fi
done
Bash has a special variable called IFS.
it's used by bash in order to know how to split a string into a list of files. (Internal Field Separator)
By default it contains space, tab and newline.
for f in `find ...
will split on the chars contained in the $IFS variable. If there is a newline or space in file names returned by the find command, it will be treated as a different file name and thus the loop will be executed for each part of the name.We could tweak the $IFS variable, but it's tricky. Let see why:
(Test this code in scripts so that the change of the IFS variable doesn't remain in your shell, you might get confused)
As you can see, now bash doesn't have any rule to split the string returned by the find command, and treats the whole output as a single value.
Clearly the problem here is that if we use
for f in `find....
we have no way to distinguish a newline contained in the file from a newline generated by the find command.Now for sake of completeness I will show you how to make your approach work, but please see an easier solution at the end of the answer.
We could use other delimiter characters and fix your current issue:
Here the
find -name "*.exe" -exec echo -n {}: \;
command returns the list of files separated by ":" (which are not legal in windows file names).Now the next step would be to actually delete those files. Take in consideration that the last file is terminated by ":" and thus bash iterates one last time with an empty string.
You should be able to perform also your basename/dirname test within this loop, as you did before.
However, this solution is ugly and works only because we have a character which we can rely on.
It turns out that the find command itself can execute commands for each file it finds.
Then yourscript.sh wil lget the full name in $1 and you can delete with
rm "$1"
even if it contains newlines and spaces.The find command can also be used to perform inline the basename/dirname test you do inside your script, but it's more advanced topic.
This will handle filenames containing any character allowed in a filename, including newlines.
See http://mywiki.wooledge.org/BashFAQ/020 and http://mywiki.wooledge.org/BashFAQ/100 for more elaboration.
You could actually use
( *.exe )
to solve this problem inbash
.Putting parenthesis like this
(
and)
around a pattern means that it will be expanded and stored in an array.Then you can iterate over each item in the array using something like this:
Here's a full example:
I can only provide a full solution after you clarify what your directory structure looks like, because I am not sure if
current_dir_name
is doing what you think it is.