-2

I have this:

find . -type f -name '*_test.go' | xargs dirname

but I get:

usage: dirname path

the output of

$ find . -type f -name '*_test.go' 

is:

./pkg_test.go
./v3/organization/organization_test.go
./v3/common/pkg_test.go
./v3/model/boil_main_test.go
./v3/model/boil_queries_test.go
./v3/model/advances_test.go
./v3/model/boil_suites_test.go
./v3/model/psql_main_test.go
./v3/model/psql_suites_test.go

anyone know how I can get the dirname of the file? Maybe I need absolute paths?

Alexander Mills
  • 9,330
  • 19
  • 95
  • 180
  • standard / non-GNU `dirname` only takes a single argument. `find . -type f -name '*_test.go' -exec dirname {} \;` –  Jan 20 '20 at 21:24
  • or, if you're _really_ using bash as your tags say, you can get rid of both `find` and `dirname`: `shopt -s globstar nullglob; gos=(./**/*.go); printf "%s\n" "${gos[@]%/*.go}"` –  Jan 20 '20 at 21:44

1 Answers1

3

Three ways:

First, your xargs way fails because you give dirname more than one pathname at a time. Instead, pass pathnames one by one to it:

find . -type f -name '*_test.go' -print0 | xargs -0 -r -I {} dirname {}

The -print0 with find and -0 with xargs passes the pathnames as a nul-delimited list instead of as a newline-delimited list. The -r option for xargs will make sure that dirname isn't run if there is no input from find.

If you know that your filenames do not contain embedded newlines:

find . -type f -name '*_test.go' | xargs -r -I {} dirname {}

Secondly, call a in-line shell script with find that runs dirname on the files one by one:

find . -type f -name '*_test.go' -exec sh -c '
    for pathname do
        dirname "$pathname"
    done' sh {} +

Alternatively, don't call dirname but use a parameter substitution in the loop (quicker):

find . -type f -name '*_test.go' -exec sh -c '
    for pathname do
        printf "%s\n" "${pathname%/*}"
    done' sh {} +

Thirdly, if you're using GNU find, use its -printf:

find . -type f -name '*_test.go' -printf '%h\n'

Lastly, if you are using zsh:

print -rC1 -- ./**/*_test.go(.ND:h)

The (.ND:h) glob modifier asks the previous globbing pattern to only expand to regular files (.), and to expand to nothing if there is no files matching the pattern (N, like nullglob in bash), and to also include hidden names (D, like dotglob in bash). The :h at the end causes the "head" of each matching pathname to be returned (i.e. the directory part of the pathname without the filename).

You could do something equivalent in bash with a loop like so:

shopt -s globstar nullglob dotglob

for pathname in ./**/*_test.go; do
    if [[ -f $pathname ]] && [[ ! -h $pathname ]]; then
        printf '%s\n' "${pathname%/*}"
    fi
done
Kusalananda
  • 320,670
  • 36
  • 633
  • 936
  • Is `-r` necessary? My local tests suggest no: if `find` results in no matches, `find ... -print0 | xargs -0 -I {} dirname {}` simply does not print to stdout and then exits with code 0. – Quelklef Mar 03 '21 at 23:01