0

I have an existing bash script that I call like this:

find /path/to/my/stuff -type d -exec sh -c 'cd "$0"; /path/to/my/script.sh function_name fn_parameter' {} \;

I frequently change the /path/to/my/stuff and fn_parameter. I also need to sometimes change the function_name.

It's getting tedious to keep retyping this command, so I would like to wrap it in another script and pass just those three parameters like this:

wrapper.sh function_name "/path/to/my/stuff" fn_parameter

Side note: I changed the order of the arguments because "function_name" is the one that changes least frequently.

I am getting overwhelmed by quoting and escaping when I try to make this wrapper script. I looked at the script in shellcheck.net and I also tried using an array for cmd (refer to one of my failed attempts below) without success. I do understand that the problem is most likely that quotes and backslashes are not being respected, but I cannot understand how to solve that.

This is one of my many failed attempts:

wrapper.sh

#!/bin/bash
function_name=$1
mpath="$2"
arg=$3

find "$mpath" -type d -exec sh -c "cd \"$0\"; /path/to/my/script.sh $function_name $arg" {} \;

Here's another example of a failed attempt:

#!/bin/bash
function_name=$1
mpath="$2"
arg=$3
cmd="'cd \$0; /path/to/my/script.sh $function_name $arg'"
echo "find \"$mpath\" -type d -exec sh -c $cmd {} \;"
find "$mpath" -type d -exec sh -c $cmd {} \;

In the above example, if I enter the output of the echo statement on the command line, it works correctly. But the wrapper fails with:

$0;: -c: line 1: unexpected EOF while looking for matching `''
$0;: -c: line 2: syntax error: unexpected end of file

For completeness, myscript.sh is similar to this:

#!/bin/bash

fn1() {
  ...
}

fn2() {
  ...
}


fn3() {
  ...
}

"$@"
4
  • $0 is the name of the current script. Seems wrong to try to cd to it... Anyway, to try to simplify this code I would separate the find (to list all arguments to act on) and the command to perform on these args. There's no need to force both to be done by find. You can use xargs and/or find ... | while read arg; do command somefunc $arg; done
    – arielf
    Sep 18, 2021 at 23:38
  • 1
    RE: $0 is the name of the current script. Seems wrong to try to cd to it. My reply: In this context, $0 is the parameter (a directory) provided by the find command.
    – MountainX
    Sep 18, 2021 at 23:50
  • @arielf-ReinstateMonica: if you can provide a working answer, I'll accept it. Thanks.
    – MountainX
    Sep 18, 2021 at 23:52
  • Just as an FYI, putting a command into a variable and then attempting to execute the variable is a bad pattern. Try not to do that. In general, you should use variables to hold data and create functions to hold commands Sep 19, 2021 at 6:48

2 Answers 2

4

Two variants of your code, each passing the special values into the in-line script that you call from find:

#!/bin/sh

mpath=$1
fn_name=$2
fn_arg=$3

find "$mpath" -type d -exec sh -c '
    cd "$3" && /path/to/my/script.sh "$1" "$2"' sh "$fn_name" "$fn_arg" {} \;

When a directory is found, find simply passes the values of the two variables fn_name and fn_arg into the sh -c script as the two first arguments, before the directory path argument. Inside the script, we use the two first arguments as the arguments to your script, and the third one as the directory path to cd into.

Note that $0 will contain the string sh. The shell will use this (arbitrary) string in any error messages it may produce (you show an example of this in your question). The $0 value is not part of the list of positional parameters.

Another variant that calls an in-line script with as many directory paths as possible at once:

#!/bin/sh

mpath=$1
fn_name=$2
fn_arg=$3

find "$mpath" -type d -exec sh -c '
    fn=$1 arg=$2; shift 2
    for dirpath do
        ( cd "$dirpath" && /path/to/my/script.sh "$fn" "$arg" )
    done' sh "$fn_name" "$fn_arg" {} +

By changing \; to + at the end of the find command, we call the sh -c with batches of found directory paths. The in-line script then has to loop over these and call your script for each in turn.

The in-line script starts by picking out the function name and argument from the list of positional parameters and shifts these off from that list. It then loops over the remaining arguments and calls cd and your script on each. The following is the in-line script with a bit of extra air inserted:

fn=$1    # 1st argument from find
arg=$2   # 2nd argument from find
shift 2  # remove them from the list

# Iterate over the remaining arguments
for dirpath do
    ( cd "$dirpath" && /path/to/my/script.sh "$fn" "$arg" )
done

I run the body of the loop in a sub-shell to avoid having to "cd back" each time after running your script.

I'm using sh here rather than bash as there is nothing sh lacks to run this code. Also, the find commands shown here are only using standard features.

2
  • Thank you. Both of your versions do actually work. The advantage over the answer I provided is that they work with relative paths. I prefer the first variant as the execution of "myscript.sh" is the bottleneck, not find.
    – MountainX
    Sep 19, 2021 at 13:02
  • Beware it may give unexpected results if $mpath contains .. components or $CDPATH is set. In scripts it's a good idea to use CDPATH= cd -P instead of just cd so it behaves more like a plain chdir() (you'd still have a problem with cd and a mpath=- but then again, find would choke anyway on anything starting with -). Sep 20, 2021 at 20:36
-1

Here is my proposed (and working) solution:

#!/bin/bash
function_name=$1
mpath="$2"
marg=$3

while IFS= read -r -d '' sdir
do
  cd "$sdir"
  /path/to/my/script.sh "$function_name" "$marg"
done <   <(find "$mpath" -type d -print0)

Edit: In response to the comments, this solution does not work with relative paths.

2
  • You can't use return outside of a function. continue might be better.
    – ilkkachu
    Sep 19, 2021 at 4:06
  • 1
    Note that if $mpath is a relative path, then only the first cd may be expected to succeed. The further calls to cd would try to change into subdirectories of that first directory.
    – Kusalananda
    Sep 19, 2021 at 8:18

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .