This is expected as your `basename {}`
is in double quotes. This would make it execute before find
is even invoked, which would replace the command substitution by the string {}
. This is also an issue in your later examples, where the variables gets expanded before they are actually set.
When you use sh -c
, the script that it takes as an argument to -c
should really always be single quoted as this makes quoting inside the script simpler. Any arguments of the inline script should be passed on its command line, not as text injected into the code (see "Is it possible to use `find -exec sh -c` safely?" for reasons why). In your case:
#!/bin/sh
find /data -type d -mindepth 1 -maxdepth 1 -exec sh -c '
echo "Full path: $1"
echo "Basename: $(basename "$1")"' sh {} \;
Or, with a couple of variables (and switching over to using printf
):
#!/bin/sh
find /data -type d -mindepth 1 -maxdepth 1 -exec sh -c '
pathname=$1
filename=$(basename "$pathname")
printf "Full path: %s\n" "$pathname"
printf "Basename: %s\n" "$filename"' sh {} \;
When you run an inline script with sh -c
, the arguments passed will be placed in $0
, $1
, $2
etc. Since $0
is usually the name of the shell or script, we pass the string sh
for this, and then the found pathname as $1
.
Or, more efficiently, using as few invocations of sh -c
as possible, and using parameter substitutions:
#!/bin/sh
find /data -type d -mindepth 1 -maxdepth 1 -exec sh -c '
for pathname do
printf "Full path: %s\n" "$pathname"
printf "Basename: %s\n" "${pathname##*/}"
done' sh {} +
Here, find
arranges for the inline script to be called with as many arguments as possible (as few times as possible). In each invocation, $0
gets set to sh
as before, while the other positional parameters takes their values from the found pathnames. The loop iterates over the passed pathnames ($0
is not a positional parameter, so that won't be iterated over).
I'm assuming that your ! -name .
test is to avoid finding the /data
directory itself. This will not be necessary as it's already skipped by -mindepth 1
(the /data
path is at "depth 0" since it's the top of a search path). Also, find
won't return the .
entry from directories, so the test would not remove /data
had you not used -mindepth 1
. In that case, you could instead have used ! -path /data
.
Since you're just iterating over the directories in a single directory, and since you have used the bash tag, and since you don't use find
to do any fancy tests on e.g. timestamp etc., you could instead do a basic shell loop:
#!/bin/bash
shopt -s dotglob nullglob
for pathname in /data/*; do
if [[ -d $pathname ]] && [[ ! -L $pathname ]]; then
printf 'Full path: %s\n' "$pathname"
printf 'Basename: %s\n' "${pathname##*/}"
fi
done
Here, the names in /data
are tested explicitly for whether they are directories, before information about their pathname and name is printed. The dotglob
shell option in bash
allows the *
globbing pattern to match hidden names, and nullglob
prevents the loop form running at all if /data
is empty or if the pattern otherwise does not match anything.
See "Understanding the -exec option of `find`" for general information about using find
with -exec
.
$THIS...
inecho \"$THIS..\"
will be expanded by the shell before thefind
command is run. Even after fixing that, you'll have problems withTHIS={}; ...
if the file name contains any spaces. Try passing the{}
as argument to thesh -c
script:find ... -exec sh -c 'THIS_FOO=$1; echo "$THIS_FOO"' sh {} \;
– Dec 18 '19 at 23:10{}
in the shell code. – Kamil Maciorowski Dec 18 '19 at 23:22basename {}
is{}
. – JdeBP Dec 19 '19 at 00:18