HiveBrain v1.2.0
Get Started
← Back to all entries
patternbashMinor

Running a script recursively in subdirectories

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
scriptsubdirectoriesrunningrecursively

Problem

I got tired of running the same command in multiple directories, so I thought "there has to be a way to make this easier". The commands I was running was mostly git status, git stash list, hg summary, mvn clean test, and so on and so on...

So what I did was to create a bash script that you can pass a parameter to. It will then run the same script or command in all subdirectories, until it finds a place where the script returns exit status 0 (meaning that it was run successfully), then it stops going deeper in those subdirectories.

As I am not that used to bash-scripting, I am wondering if there's anything I can improve here. I would also like you to comment on the usability of this script. You are also welcome to make feature-requests, bug reports, or pull requests (my favorite!) on my bash-recursive repository on GitHub.

The script is:

#!/bin/bash

RUN_SCRIPT=$1

recurseCheck() {
  local f
  for f in $1/* ; do
    local PREV_DIR=`pwd`
    if [ -d $f ]; then
      cd $f
      RESULT=`eval $RUN_SCRIPT`
      local RESULT_CODE=$?
      if [ $RESULT_CODE -eq 0 ]; then
        echo "$f"
        echo "$RESULT"
        echo ""
      else
        recurseCheck $f
      fi
      cd $PREV_DIR
    fi
  done;
}

START_DIR=`pwd`
recurseCheck $START_DIR


Usage example:


recurse.sh "git status 2>&1"

Will output something like:

/home/zomis/gitstuff/a/Duga
On branch master
nothing to commit, working directory clean

/home/zomis/gitstuff/a/SudokuSharp
On branch master
nothing to commit, working directory clean

/home/zomis/gitstuff/c/Brainduck
On branch master
nothing to commit, working directory clean

/home/zomis/gitstuff/c/CodeReview-Shield
On branch master
nothing to commit, working directory clean

/home/zomis/gitstuff/c/SE-Scripts
On branch master
nothing to commit, working directory clean


The reason 2>&1 is there is because otherwise this, which goes to stderr would be printed:

```
fatal: Not a git repository (or any of the parent direc

Solution

Check yourself before you wreck yourself...

shellcheck.net is an awesome website that checks your Bash syntax.
Among other things, it will tell you to:

  • Double-quote your path variables



  • Use $(...) instead of the legacy `... for sub-shells



Avoid
cd inside scripts

cd inside scripts is troublesome.
In case something goes wrong,
you might find yourself in unexpected places, and wreak havoc.

But if you must, like in your example,
there's a better way than saving the previous directory in
PREV_DIR and then cd "$PREV_DIR" to go back, using a (...) sub-shell environment:

(
cd "$f" || break
RESULT=$(eval "$RUN_SCRIPT")
local RESULT_CODE=$?
if [ $RESULT_CODE -eq 0 ]; then
    echo "$f"
    echo "$RESULT"
    echo
else
    recurseCheck "$f"
fi
)


Notice that there is only one
cd in, no cd out.
This is because the cwd changes only affect the environment inside the
(...).

Also notice the
cd "$f" || break: if the cd command fails for some reason,
you probably don't want to execute the rest of the block.
You might go as far as
exit at that point (shellcheck recommends it too) and investigate the troublesome directory.

Minor things

You had a pointless
; in done;, and instead of echo "" you can write simply echo.

Input validation

What if you run this script without arguments?
eval ""` exits with success, so it will simply print all directories.
If that's ok, then everything's fine.
If not, then you might want to add some input validation, for example:

if [ ! "$RUN_SCRIPT" ]; then
    echo usage: $0 'some script'
    exit 1
fi


Suggested implementation

Putting it together (coming to you in a Pull Request very soon):

#!/bin/bash

RUN_SCRIPT=$1

recurseCheck() {
    local f
    for f in $1/* ; do
        if [ -d "$f" ]; then
            (
            cd "$f" || break
            RESULT=$(eval "$RUN_SCRIPT")
            local RESULT_CODE=$?
            if [ $RESULT_CODE -eq 0 ]; then
                echo "$f"
                echo "$RESULT"
                echo
            else
                recurseCheck "$f"
            fi
            )
        fi
    done
}

START_DIR=$(pwd)
recurseCheck "$START_DIR"

Code Snippets

(
cd "$f" || break
RESULT=$(eval "$RUN_SCRIPT")
local RESULT_CODE=$?
if [ $RESULT_CODE -eq 0 ]; then
    echo "$f"
    echo "$RESULT"
    echo
else
    recurseCheck "$f"
fi
)
if [ ! "$RUN_SCRIPT" ]; then
    echo usage: $0 'some script'
    exit 1
fi
#!/bin/bash

RUN_SCRIPT=$1

recurseCheck() {
    local f
    for f in $1/* ; do
        if [ -d "$f" ]; then
            (
            cd "$f" || break
            RESULT=$(eval "$RUN_SCRIPT")
            local RESULT_CODE=$?
            if [ $RESULT_CODE -eq 0 ]; then
                echo "$f"
                echo "$RESULT"
                echo
            else
                recurseCheck "$f"
            fi
            )
        fi
    done
}

START_DIR=$(pwd)
recurseCheck "$START_DIR"

Context

StackExchange Code Review Q#112154, answer score: 8

Revisions (0)

No revisions yet.