Run command from command line inside directories from glob in bash shell script

In a bash shell script, do-for.shI want to execute a command in all directories named in glob using bash. This has been answered, but I want to provide a command on the command line. In other words, assuming I have directories:

  • foo
  • bar

I want to enter

do-for * pwd

and bash print the working directory inside fooand then inside bar.

From reading meaningless answers on the Internet, I thought I could do this:

for dir in $1; do
  pushd ${dir}
  $2 $3 $4 $5 $6 $7 $8 $9
  popd
done

Obviously, although glob *expands to another command line argument variable! So, the first time through the loop, for $2 $3 $4 $5 $6 $7 $8 $9I expected foo pwd, but instead it appears, I get foo bar!

glob ? ?

, . ( Windows, .)

./do-for.sh repo-* git commit -a -m "Added new files."
+4
9

, , - ,

./do-for.sh repo-* -- git commit -a -m "Added new files."

script - ( , ):

CURRENT_DIR="$PWD"

declare -a FILES=()

for ARG in "$@"
do
  [[ "$ARG" != "--" ]] || break
  FILES+=("$ARG")
  shift
done 

if
  [[ "${1-}" = "--" ]]
then
  shift
else
  echo "You must terminate the file list with -- to separate it from the command"
  (return, exit, whatever you prefer to stop the script/function)
fi

, "$ @" . :

for FILE in "${FILES[@]-}"
do
  cd "$FILE"
  "$@"
  cd "$CURRENT_DIR"
done

, , "-", ( - ).

+6

, , script undefined, .

#!/bin/bash
CMND=$(eval echo "\${$#}")        # get the command as last argument without arguments or
while [[ $# -gt 1 ]]; do          # execute loop for each argument except last one
     ( cd "$1" && eval "$CMND" )  # switch to each directory received and execute the command
     shift                    # throw away 1st arg and move to the next one in line
done

: ./script.sh * pwd ./script.sh * "ls -l"

, (./ script.sh * ls -l), script , , , ( , ).

script, : ./script.sh <dirs...> <command> <arguments...> : ./script.sh * ls -la

# Move all dirs from args to DIRS array
typeset -i COUNT=0
while [[ $# -gt 1 ]]; do
    [[ -d "$1" ]] && DIRS[COUNT++]="$1" && shift || break
done

# Validate that the command received is valid
which "$1" >/dev/null 2>&1 || { echo "invalid command: $1"; exit 1; }

# Execute the command + it arguments for each dir from array
for D in "${DIRS[@]}"; do 
     ( cd "$D" && eval "$@" )
done
+3

:

#!/bin/bash

# Read directory arguments into dirs array
for arg in "$@"; do
    if [[ -d $arg ]]; then
        dirs+=("$arg")
    else
        break
    fi
done

# Remove directories from arguments
shift ${#dirs[@]}

cur_dir=$PWD

# Loop through directories and execute command
for dir in "${dirs[@]}"; do
    cd "$dir"
    "$@"
    cd "$cur_dir"
done

, , , dirs. , , .

shift, cur_dir.

, .

./do-for.sh repo-* git commit -a -m "Added new files."

example – repo-* -, , script , .

, , , glob , --, , glob , .

+2

Windows, , . , Windows , ( -), Linux/Unix . Windows, Linux , - .

bash,

   ./do-for.sh repo-'*' git commit -a -m "Added new files."

   ./do-for.sh repo-\* git commit -a -m "Added new files."

- , , . , , , ( , do-for.sh script, , .)

  • . , , , , , reset .

  • :

    ./do-for.sh repo-* -- git commit -a -m "Added new files."
    

, , , , , ( , , .)

  • :

    ./do-for.sh repo-* 'git commit -a -m "Added new files."'
    

, , , , , .

  • :

     ./do-for.sh repo-* git commit -a -m "Added new files."
    

, , , . , (, , ).

- . , * script. ( , split, non-globbed , .) ( ). :

        # repo- is a prefix: the command will be excuted in all
        # subdirectories whose name starts with it
        ./do-for.sh repo- git commit -a -m "Added new files."

        # The command will be excuted in all subdirectories
        # of the current one
        ./do-for.sh . git commit -a -m "Added new files."

        # If you want the command to be executed in exactly 
        # one subdirectory with no globbing at all, 
        # '/' can be used as a 'stop character'. But why 
        # use do-for.sh in this case?
        ./do-for.sh repo/ git commit -a -m "Added new files."

        # Use '.' to disable the stop character.
        # The command will be excuted in all subdirectories of the
        # given one (paths have to be always relative, though)
        ./do-for.sh repos/. git commit -a -m "Added new files."

, , SQLs % character

        # the command will be excuted in all subdirectories 
        # matching the SQL glob
        ./do-for.sh repo-% git commit -a -m "Added new files."
        ./do-for.sh user-%-repo git commit -a -m "Added new files."
        ./do-for.sh % git commit -a -m "Added new files."

, , bash.

:

#!/bin/bash
if [ "$#" -lt 2 ]; then
  echo "Usage: ${0##*/} PREFIX command..." >&2
  exit 1
fi

pathPrefix="$1"
shift

### For second version, comment out the following five lines
case "$pathPrefix" in
  (*/) pathPrefix="${pathPrefix%/}" ;;   # Stop character, remove it
  (*.) pathPrefix="${pathPrefix%.}*" ;;  # Replace final dot with glob
  (*) pathPrefix+=\* ;;                  # Add a final glob
esac
### For second version, uncomment the following line
# pathPrefix="${pathPrefix//%/*}"        # Add a final glob

tmp=${pathPrefix//[^\/]}   # Count how many levels down we have to go
maxDepth=$((1+${#tmp}))


# Please note that this won’t work if matched directory names
# contain newline characters (comment added for those bash freaks who 
# care about extreme cases)
declare -a directories=()
while read d; do
  directories+=("$d")
done < <(find . -maxdepth "$maxDepth" -path ./"$pathPrefix" -type d -print)

curDir="$(pwd)"
for d in "${directories[@]}"; do
  cd "$d";
  "$@"
  cd "$curDir"
done

Windows, ,

        ./do-for.sh 'repository for project' git commit -a -m "Added new files."

( , , , , , , % -patterns .)

Windows Linux, , ..

+2

bash "set -o noglob", (globs). script, , .

+1

find-while-read - . -

#!/bin/bash
myfunc(){
 cd "$2"
 eval "$1" # Execute the command parsed as an argument
}
cur_dir=$(pwd) # storing the current directory
find . -type d -print0 | while read -rd '' dname
do
 myfunc "pwd" "$dname"
 cd "$cur_dir" #Remember myfunc changes the current working dir, so you need this
done
+1

, find, , :

do_for() { find . -type d \( ! -name . \) -not -path '*/\.*' -name $1 -exec bash -c "cd '{}' && "${@:2}" " \;  }

- do_for repo-* git commit -a -m "Added new files." : * , :

do_for \* pwd 
+1

script. .

, script

#!/usr/bin/env bash

for dir in $1; do (
    cd "$dir"
    "${@:2}"
) done

, , . :

mkdir test_dir1 test_dir2
./do-for.sh "test_dir*" git init
./do-for.sh "test_dir*" touch test_file
./do-for.sh "test_dir*" git add .
./do-for.sh "test_dir*" git status
./do-for.sh "test_dir*" git commit -m "Added new files."
+1
source

No one offers a solution using find? Why not try something like this:

find . -type d \( -wholename 'YOURPATTERN' \) -print0 | xargs -0 YOURCOMMAND

Take a look man findfor more options.

+1
source

Source: https://habr.com/ru/post/1664020/


All Articles