4

I have a bash script which does similar to the following:

for src in $(find -H "$DOTFILES_ROOT" -maxdepth 2 -name '*.sym' -not -path '*.git*')
 do
   dst="$HOME/$(basename "${src%.*}")"
   link_file "$src" "$dst"
 done

I've ran shellcheck on my script and it returned

For loops over find output are fragile. Use find -exec or a while read loop.

Which I've read an understood, and it makes sense to me e.g.

find -H "$(pwd)" -maxdepth 2 -name '*.sym' -not -path '*.git*' -exec echo {} \;

But I'm not sure how to make it work with a variable as I have in the for loop

1 Answers1

1

The variables in the for loop that you have are src and dst. src is the pathname of a file given to the loop by find and dst is computed from src.

Your loop can be converted to a find -exec command like this:

find -H "$DOTFILES_ROOT" -maxdepth 2 -type d -name '.git' -prune -o \
    -type f -name '*.sym' -exec sh -c '
        link_file "$1" "$HOME/$( basename "${1%.*}" )"' sh {} ';'

I have assumed that you by -not -path '*.git*' intend to not look in .git directories. This is the same as -type d -name '.git' -prune in the command above, but with -prune we stop find from even entering those directories.

Once we have weeded out the .git directories, we look for .sym files. When one is found, the command from your loop is executed. The command is executed through sh -c (to be able to do fancy things with parameter substitutions and basename) and the pathname of the found file is made available inside as $1.

One could even incorporate your loop (almost) as you have written it:

find -H "$DOTFILES_ROOT" -maxdepth 2 -type d -name '.git' -prune -o \
    -type f -name '*.sym' -exec sh -c '
        for src do
            dst="$HOME/$( basename "${src%.*}" )"
            link_file "$src" "$dst"
        done' sh {} +

In this case, the sh -c script is given not a single pathname but a number of them (due to the + at the end of the -exec), so we loop over these and perform the action.

Related:

Kusalananda
  • 333,661