I am making a script to simplify my daily tasks. Everyday, I have to grep for a few things inside a company server. It was okay, however, now, they have segregated each object into sub directories. I am looking for a solution in which my existing shell script will execute repeatedly into each sub directory inside a certain directory. How do I do this? I am quite new to Linux and still learning the ropes.

Here's my script:

# cd into dir
cd $MainDir

for dir in $(ls); do

#fetch start time of uut
grep -i "01_node_setup" $dir/his_file | tail -1 >> /home/xtee/sst-logs.out

#check if sysconfig.out exists
if [ -f $dir/sysconfig.out];
    grep -A 1 "Drive Model" $dir/sysconfig.out | tail -1 >> /home/xtee/sst-logs.out
    grep -m 1 "Pair0 DIMM0" $dir/node0/trans_file_prev/*setupsys* | tail -1 >> /home/xtee/sst-logs.out


#cd back to where we started

A nice guy helped me with the script, and the script above is what came out. It worked alright however, the sst-logs.out file displays binary file /some/subdirectory/ instead of the output of the grep. What is wrong in the script?

  • anyone who can help me? :( – JudeJitsu Aug 2 '12 at 9:56
  • Does the grep switch -R for recursive help? – Marco Aug 2 '12 at 10:00
  • @Marco, it might if the subdirectories contain only one each file name i am grepping to which is not the case. the subdirectories also have their own subdirectories which has the file with the same file name (e.g. history) which i do not want... – JudeJitsu Aug 2 '12 at 10:10

find will save you:

find /your/dir/with/subdirs -type d -exec sh -c 'cd "{}" ; /path/to/your/script.sh ;' \;
  • Thanks rush... Will also try this. I am looking for the lightest implementation.. :) – JudeJitsu Aug 2 '12 at 10:15

There are several problems in your script.

cd $MainDir

Better make this cd "$MainDir". Here this is not important, but it's a good habit to get into. If you leave a variable expansion unquoted, the value of the variable is split into words, and each word is treated as a shell wildcard pattern; so this command would fail if $MainDir contained whitespace or \[?*. Putting double quotes never hurts (unless you want splitting and filename matching to happen), and avoids these extra expansions. The same goes for command substitutions ($(somecommand)). Always put double quotes around variable and command substitutions unless you know you want splitting and filename matching.

for dir in $(ls); do

Don't parse the output of ls. You almost never need ls in scripts. As explained above, the result of a command substitution is split into words that are treated as wildcard patterns, so $(ls) mangles whitespace and wildcard characters, in addition to the mangling of non-printable characters that ls may do. Listing the file names in a directory is built into the shell:

for dir in *; do

If you make that for dir in */; do, only subdirectories of the current directory are matched, not other kinds of files.

if [ -f $dir/sysconfig.out];

You need whitespace on each side of the brackets, except when the character next to it is a shell punctuation character: ();&|<>. And again, put double quotes around the variable substitutions (this goes for every other place where you have a variable substitution): if [ -f "$dir/sysconfig.out" ];

#cd back to where we started

This is useless: the process is about to exit, changing its current directory won't affect anything. Note that each process has its own current directory: changing it in a script does not affect the program that called the script.

Turning to the reason you asked this question, it isn't clear what you want. “binary file /some/subdirectory/” doesn't make much sense: you wouldn't get this message with a directory. For better help, copy-paste the error message. You should always copy-paste error messages when you ask for help online.

From your description, it seems that files have moved one directory level deeper. If that's the case, change the line for dir in */; do to

for dir in */*/; do

There are numerous ways you could do this. you could either run your existing script in a for loop, or use find and xargs, or perhaps even make use of grep's -r aka --recursive option.

The simplest is probably just to write a for loop wrapper around your existing script. For example:

#! /bin/bash
cd /path/to/top/level/directory

for d in */ ; do 
  pushd $d
  # call your script here
  • Thanks Craig. Will try this... Appreciate the help :) Can this be done to CD just one sub directory deep? Because the subdirectories have their own "history" subdirectory which contains the same file name. – JudeJitsu Aug 2 '12 at 10:12
  • as written, it only iterates through the first-level subdirectories of your top-level directory. i.e. yes. if you needed it to process every subdirectory below that you'd have to do something like for d in $(find . -type d); do ... – cas Aug 2 '12 at 10:24

Slight improvement on @rush's find suggestion, instead of using the -exec switch, you can avoid adding the cd command by doing something like this:

find /path/to/dir/containing/subdirs -type d -execdir /path/to/your/script.sh \;

This way you can avoid the for loop in your original script altogether.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.