Execute command in all immediate subdirectories
Asked Answered
P

4

47

I'm trying to add a shell function (zsh) mexec to execute the same command in all immediate subdirectories e.g. with the following structure

~
-- folder1
-- folder2

mexec pwd would show for example

/home/me/folder1
/home/me/folder2

I'm using find to pull the immediate subdirectories. The problem is getting the passed in command to execute. Here's my first function defintion:

mexec() {
    find . -mindepth 1 -maxdepth 1 -type d | xargs -I'{}' \
    /bin/zsh -c "cd {} && $@;";
}

only executes the command itself but doesn't pass in the arguments i.e. mexec ls -al behaves exactly like ls

Changing the second line to /bin/zsh -c "(cd {} && $@);", mexec works for just mexec ls but shows this error for mexec ls -al:

zsh:1: parse error near `ls'

Going the exec route with find

find . -mindepth 1 -maxdepth 1 -type d -exec /bin/zsh -c "(cd {} && $@)" \;

Gives me the same thing which leads me to believe there's a problem with how I'm passing the arguments to zsh. This also seems to be a problem if I use bash: the error shown is:

-a);: -c: line 1: syntax error: unexpected end of file

What would be a good way to achieve this?

Pone answered 9/1, 2017 at 17:13 Comment(0)
D
109

Can you try using this simple loop which loops in all sub-directories at one level deep and execute commands on it,

for d in ./*/ ; do (cd "$d" && ls -al); done

(cmd1 && cmd2) opens a sub-shell to run the commands. Since it is a child shell, the parent shell (the shell from which you're running this command) retains its current folder and other environment variables.

Wrap it around in a function in a proper zsh script as

#!/bin/zsh

function runCommand() {
    for d in ./*/ ; do /bin/zsh -c "(cd "$d" && "$@")"; done
}

runCommand "ls -al"

should work just fine for you.

Dependence answered 9/1, 2017 at 17:22 Comment(5)
ls -al in this case is just an example command I'm running in every sub-directory. I'd want it to be free-form, regardless of what command I use. I'll try the for loop.Pone
@Zahymaka: I have added a more generic way of doing this, by adding a function. Don't forget to accept/upvote it once you find it solving your problem.Dependence
This function doesn't work if there's a quote in the directory name (zsh:1: unmatched ')Bazooka
Nice. Seeing your quotes prompted me to try running my original mexec with quotes. What do you know, it worked!Pone
Good. If anyone needs to execute commands in all sub-directories at two levels deep, you can simply use ./*/*/Expectorant
B
6
#!/bin/zsh
# A simple script with a function...

mexec()
{ 
  export THE_COMMAND=$@
  find . -type d -maxdepth 1 -mindepth 1 -print0 | xargs -0 -I{} zsh -c 'cd "{}" && echo "{}" && echo "$('$THE_COMMAND')" && echo -e'
}

mexec ls -al
Bazooka answered 10/1, 2017 at 18:29 Comment(5)
This one should work even with directories with quotes and other strange charactersBazooka
The two echos are a hack. Not sure why -e wouldn't work on the first one.Bazooka
I just realized my original works if I call mexec 'ls -al' with the command in quotes. So does this. Thanks!Pone
Just remember your method still won't work with directories containing many directory legal characters. Your script will give an error "xargs: unmatched single quote; by default quotes are special to xargs unless you use the -0 option" or similar depending on the characters used.Bazooka
Got it. Very unlikely I'll have directories with quotes in them.Pone
S
3

using https://github.com/sharkdp/fd but you could as well use plain old find instead of fdfind

function inDirs() { fdfind --type d --max-depth 1 --exec bash -c "x={} && echo && echo \$x && echo \${x//?/=} && cd {} && echo '-> '$* && $*" ; }
Snowdrift answered 7/12, 2021 at 18:51 Comment(0)
T
0

If you want a parallelized version of this:

#!/bin/bash

# Command to run in each directory
COMMAND="ls -l"

# Export the command so it is available to xargs
export COMMAND

# Function to run the specified command in a directory
run_command() {
    dir="$1"
    eval "$COMMAND $dir" # this runs the command on $dir, like 'go build ./mydir'
}

export -f run_command

# Find all directories and run the command in parallel
# -type d finds directories
# -maxdepth 1 ensures only the immediate subdirectories are found
# -mindepth 1 excludes the current directory (.)
# -print0 and -0 handle directories with spaces in their names
find . -type d -maxdepth 1 -mindepth 1 -print0 | xargs -0 -n 1 -P 4 -I {} bash -c 'run_command "$@"' _ {}

# Note: Adjust the -P flag to change the number of parallel processes
Torn answered 10/2 at 13:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.