Search code examples
bashsh

BASH Linux- How to pass the result of find into a for loop


I get a local directory that contains different Git repos in it. The directory tree is something like this:

Dir
- dir1  --- repo1
- dir2  --- repo2
- dir3  --- repo3

I want to create a Bash script that use the find command to get all the .git directory names, and then pass all the paths in a for loop to git pull all the updates from the remote repo. Therefore, I can update all the repos by running this bash script.

Can anyone give a hint on how to do this? Relevant documentation is also helpful.


Solution

  • I'm going to suggest a rather different approach, one which exploits find's capability to run commands.

    cd Dir             # Change to the top-level directory
    find . -type d -name .git -exec \
         sh -c 'for d in "$@"; do (cd "$d/.."; git pull) done' argv0 {} +
    

    This changes directory to the top level directory, and then runs find to locate the .git directories. The command after the -exec is executed, with each Git directory name taking the place of {} in the script, and the + means as many such names as are convenient will be used in a single command execution. The command executed is:

    sh -c 'for d in "$@"; do (cd "$d/.." && git pull) done' argv0 dir1/.git dir2/.git …
    

    The argv0 argument is the $0 of the shell script run via -c '…' — you could use a name such as git-multi-pull if you preferred. That script processes each argument in turn, carefully preserving spaces, through (cd "$d/.." && git pull) which changes directory to the top-level directory of the repository and runs git pull. The (…) runs the operations in a sub-shell, which means that if there is a problem (hard to see what it would be, but just in case), only the one child sub-shell is affected. There's no need to try to get back to the correct location for the next cd — the previous sub-shell exited without affecting the sh -c '…' shell. The advantage of this is that all the eccentric characters in file names are handled automatically and completely.

    This is the 'type it at the command line' version. You could make it into a script by removing the cd Dir and replacing . with "${@:-.}" in the find command. You then specify the directory or directories to be searched for Git repos as command line arguments, or use the current directory if there are no command line arguments.

    You can add a pwd or echo "$d" to the shell script to echo directory names as you go, which gives you progress tracking. If you wanted, you could demonstrate that the argv0 is the $0 with:

    sh -c 'for d in "$@"; do (cd "$d/.." && echo "$0: $PWD" && git pull) done' git-multi-pull {} +
    

    If you're into cryptic shell scripting, you can omit the in "$@"; it is implied when you write for d; do (…) done. Also note that when the whole body of the loop is a sub-shell, you don't need a semicolon before the done; the ) suffices.