Write a shell script which will move all files in the current directory containing the word hello
into a separate folder called hello-world
.
The current directory contains files a b c d e
and f
out of which files a
, c
and f
contain the word hello
.
How can I search for the word hello
in files of the current directory and move the files satisfying the condition into another directory hello-world
?
Best Answer
Let's break this down into smaller tasks. We need to
hello-world
, if it doesn't existhello
Making a directory is done with
mkdir
. It will always refuse to create a directory that exists (thereby refusing to overwrite such directories) but it also has a flag-p
which, as mentioned inman mkdir
doesn't bother to tell you if the directory you are trying to create already exists.
So the first command of our script could be (check you are in the right directory first).
The second step is identifying the files we want. The most obvious command to search for text in files is
grep
. If we wanted only the filenames of the files that containhello
, we can use the-l
flag, which, according toman grep
suppresses the normal output. But, although we can get away with it for your example, we really don't want the filenames as output, because the output of a command is just text, and filenames might contain all sorts of characters (from the humble space to the exotic newline) that will cause the shell to see something different from the actual name of the file you want to do something to, and behave in a way you didn't want. For example, having set up a test directory for your script, if I add a file that has a space in its name, see what happens:
The file
with space
is treated as two separate files.To identify files in order to have the shell do something with them, we should avoid parsing filenames. We could test each file, see whether it contains the text, and if it does, move it. To loop over files, as shown above, we can use
for
which has syntax like this:Inside a
for
loop (and anywhere else too) we can use thetest
command, which sometimes looks like[
and is also like[[
to make conditional statements, and if you only wanted to check whether the file was empty or not, or was of a particular type, I would advise you to use thetest
command.But you want to find some particular text, so we ought to call
grep
, but we only really want to know if looking forhello
in the file succeeded, so we know that we then have to do something with the file.To make a conditional statement based on the success of a command, we can use
if
.if
is often used withtest
/[
/[[
, but you don't need those, becausegrep
has another useful flag:Zero in Bash means success. So to do what we want, we could write something like
(the
fi
is part of theif
command's syntax. It tells the shell you've finished yourif
)I usually write shell scripts in an interactive shell and only scriptify them into a file later if at all, because I'm lazy and I mostly only write very trivial scripts. Anyway, you can write a short script as a one-line command by separating commands with
;
. If something goes wrong, hit the up arrow key to edit the last command...We don't want to run that command once for each file. That would defeat the object of writing a script to do the job and save us typing, so to loop over the files we can use
for
. To avoid getting an error fromgrep
about thehello-world
directory, we can add another flag to exclude it.Here's my test command to do this job and the output I get from it in my test environment:
This script doesn't move any files, because of
echo
beforemv
which shows what command will be run with each iteration of the loop. Removeecho
after testing if the commands look right (note that you can't always reliably useecho
for testing, and you might have to introduce some temporary quoting to do so, but it works fine here).*
is all non-hidden files in the current directory. It is safer to use./*
which means the same thing, but ensures all paths start with./
(which is the address of the current working directory.
), preventing filenames starting with-
being interpreted as options. We have dealt with that possibility by adding--
togrep
and to themv
command to indicate the end of options.-v
ismv
's verbose flag.We should quote variables to suppress shell expansions. If I remove the quotes around
"$file"
, my output isHere's the script as a script:
Don't forget to remove
echo
when you're ready to move the files for real.PS, there might well be more efficient ways to do this. My way is just an example.