0

I currently have this command, which successfully prints each file name on its own line:

find . -exec echo {} \;

I'm trying to split the logic so that the find command executes a function. Based on this question I've tried this:

my_function() {
    echo $1
}
export -f my_function
find . -exec bash -c 'my_function "$@"' bash {} +

Then I get this output:

.

I also tried replacing $@ with $* but this causes $1 to be every single file without line breaks. I would like to run logic that checks each file, so I would like $1 to just be one file at a time. I tried splitting the output with spaces via for file in $1 but this doesn't work for files that have spaces in the file name. How do I run a Bash function for every file found by the find command?

EDIT: Here's the whole script I am using. It seems to work fine.

# Ensures that non-csproj text files are formatted with LF line endings.
format() {
    for pathname do
        if [[ $pathname == *"git"* ]]; then
            continue
        elif [[ $pathname == *"csproj"* ]]; then
            continue
        fi
        dos2unix $pathname
    done
}
export -f format
find . -exec bash -c 'format "$@"' bash {} \;
3
  • 1
    Change $1 to "$1" aka Quote your variable. – Jetchisel Mar 19 '20 at 22:06
  • What are you expecting that test against *git* to do? Do you intend to test against the filename, or, like you're currently doing, the whole pathname? – Kusalananda Mar 19 '20 at 22:36
  • I want the script to ignore the .git folder. It doesn't need to be perfect, but if you have a better way I'd appreciate it. – Aaron Franke Mar 19 '20 at 22:46
4

To run dos2unix --newline on every regular file in and below the current directory, avoiding any file whose name contains the string git:

find . -type f ! -name '*git*' -exec dos2unix --newline {} +

That is, find all regular files whose names do not match the pattern *git*, and run dos2unix --newline on all of these in as large batches as possible at once. Change ! -name '*git*' to ! -path '*git*' to avoid any file whose pathname contains the string git (e.g. files in .git directories).

To explicitly avoid any .git directories, but to include anything else that may have git in its name:

find . -name .git -prune -o -type f -exec dos2unix --newline {} +

This stops find form even entering any directory called .git by using -prune to delete such paths from the search tree.


Answer before edit to the question:

Your function prints out its first argument only. The dot is the top-level search path that you use with find. It gets passed as you don't do any particular filtering of the directory entries (as with, for example, -type f for regular files only, or -name, or any other type of find test).

If you want your function to print each of its arguments, use either

my_function() {
    printf '%s\n' "$@"
}

which lets printf print each of the arguments with a newline in-between, or

my_function() {
    for pathname do
        printf '%s\n' "$pathname"
    done
}

which loops over the arguments and calls printf once for each.

This would be expected to work correctly if you called the function like

my_function "$@"

from within your in-line bash -c script. The "$@" expands to all the arguments given to the script, individually quoted.

Another way would be to move the looping into the bash -c script:

for pathname do
    my_function "$pathname"
done

and then have

my_function () {
    printf '%s\n' "$1"
}

This would be explicitly doing what you say you'd want to do, i.e. call the function once for each pathname.

The find command would then look either like

find . -exec bash -c 'for pathname do my_function "$pathname"; done' bash {} +

or, arguably a bit more readable,

find . -exec bash -c '
    for pathname do
        my_function "$pathname"
    done' bash {} +

This would, incidentally, be the same as

shopt -s globstar nullglob dotglob

for pathname in ./**/*; do
    my_function "$pathname"
done

except that . would not be processed. Using this, you would not have to export your my_function function.

With the loop inside the function (as in the first two pieces of code in this answer), this would be shortened to

shopt -s globstar nullglob dotglob

my_function ./**/*

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.