find . -type f -exec echo {} \;
Using the above command, I want to get file names without leading "./" characters. So basically, I want to get:
filename
Instead of:
./filename
Any way to do this?
Use *
instead of .
and the leading ./
disappears.
find * -type f
Note that it's the shell that first expands that *
to the sorted list of non-hidden (by default) files in the current working directory and passes them instead of .
as separate arguments to find
. That has several implications that should be noted:
find
).!
, (
, )
or with a name starting with -
, with possible nasty consequences if there's a file called -delete
for instance.*
is expanded by the shell to a list of all files that is then passed to find
. Depending on the shell and its settings that listing may or may not contain files whose names start in a dot. That may be different from what find
would do if it were given .
. For bash you can use find * .* -type f
to list all files.
– Hannes
Dec 29 '20 at 22:45
.*
but .[^.]* *
, else find will explore ..
See my answer for the full command. BTW, this is quite depressing to notice that this answer only repeats one of the commands I posted 3 years before, and yet this answer has twice more votes than mine despite it is incomplete (hidden files are omitted) and despite I warned such a construct should be avoided. Go figure... clearly, people prefer short answers, even if such answers fail in certain situations.
– xhienne
Apr 02 '21 at 09:20
-name
.
– Johannes
Aug 08 '22 at 20:58
Assuming that you don't want the filename alone, but also the full relative path without the leading ./
, if you have GNU find
you can use:
find . -type f -printf '%P\n'
From find(1) man page:
%P
- File's name with the name of the starting-point under which it was found removed.
Else, you can try one of these:
find . -type f -print | cut -d/ -f2-
find .[!.]* * -type f -print
You should avoid this last one (think of the excessively long command line if you have a lot of entries in your directory).
-print
does the same thing as your -exec echo {} \;
but is much better: no external process call so lower overhead and no undesirable side-effects with filenames beginning with a dash.
.[^.]*
syntax does not work in any (posix) shell that I am aware of.
– Juan
Jul 12 '21 at 15:19
.[^.]*
syntax or that includes .
and ..
in the resulting pathname expansion. Would you be kind enough to name it and explain what does not work? I do agree this is not strictly POSIX compliant and one should use .[!.]*
instead, but I have used this syntax on many platforms for more than 30 years and never had a problem with it.
– xhienne
Jul 14 '21 at 11:33
.[^.]*
syntax does not match strings that start with a dot followed by a non-dot: mksh r59, dash 0.5.10.2, ksh 2020.0.0, pdksh 5.2.14 - some cause the error: "find: .[^.]: No such file or directory". However, it looks liks POSIX currently does* specify bracket expressions as valid (and not optional) for pattern matching (see the 'Shell and Utilities' document, 'Pattern Matching Notation': https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13). So the shells where it does not work appear to be deficient as far as POSIX is concerned.
– Juan
Aug 20 '21 at 14:40
ls -d .[^.]*
with dash, it only lists ..
, but ls -d .[!.]*
lists dot files and not .
nor ..
. So dash does support bracket expressions, albeit a bit differently than bash. It looks like dash doesn't support the ^ negation at all (which is not what posix is specifying as I read it).
– Juan
Aug 20 '21 at 15:35
.[!.]*
works with more shells than .[^.]*
does. The ksh flavors and dash seem to only accept ! for character class negation and not ^ - unless there is some way to properly quote the ^ for dash & ksh and get it working (but I didn't figure out a way to do that in some quick quoting / escaping attempts). Maybe an edit to this answer that describes this would be helpful.
– Juan
Aug 20 '21 at 15:42
find . -type f -exec echo {} \;
Default action of find
is to print results. When not explicitly told, find
defaults to searching current directory. Your command can be simplified to find -type f
.
Why/when does this matter? Only when you run this command on a sufficiently large directory, you will start to see the performance difference. -exec echo {} \;
will make your computer work needlessly more because it has to start an external process like /bin/echo
to print the filenames. One instance of echo executable is run per file found by find
.
To answer your question about removing ./
, a more efficient method would be to use cut
. You know that the first two characters are always ./
. cut -c3-
will only retain characters from position 3 and beyond.
> cd /etc/fonts/conf.d
> find -type f | cut -c3-
99pdftoopvp.conf
00kde.conf
65-khmer.conf
README
This fails to work as expected if your filenames contain new line character, but that is another whole new story.
find -type f
) is not currently POSIX compatible behavior. This fails with variants of find(1) that are not GNU find.
– Juan
Jul 12 '21 at 15:22
cut
cuts each line but there's nothing stopping a file path from containing newline characters.
– Stéphane Chazelas
Jul 05 '23 at 16:19
The GNU version of find provides the builtin action -printf format which allows you to format the output of your query.
So to answer your question
find . -type f -printf '%f\n'
Will output only the filenames with no leading characters (or pathnames) with a newline so each filename is on it's own line
– Guillaume Berche Jul 05 '23 at 15:40%f File's name with any leading directories removed (only the last element).
The answers with printf
are missing the point for the case where I want to run a command via -exec
on the params and echo
is just an example. In reality I may want to run my-command
on the found files instead of echo
Here's a maybe not efficient but working solution:
find . -type f -exec sh -c 'echo ${0#./}' "{}" \;
Explanation:
sh -c 'echo $0' foobar
runs a subshell which prints $0
, and foobar
is passed as $0
, so it prints foobar
$0
, we actually use ${0#./}
, which means "use $0
, but strip leading ./
(#./
) - this is parameter expansion{}
from exec (the found filename from find
with leading slash) as $0
to sh -c
So in the end to run my-command
on found files without leading ./
, you'd do:
find . -type f -exec sh -c 'my-command ${0#./}' "{}" \;
In:
find path/to/dir some/file other-file other/dir <criteria> -print
Or:
find path/to/dir some/file other-file other/dir <criteria> -exec cmd -- {} ';'
We tell find
to find files matching the <criterial>
starting with the list of file paths given as initial arguments.
If path/to/dir
above matches the <criteria>
, it will be printed or cmd
will be called with --
and path/to/dir
as arguments. Same for some/file
or other/dir
.
If path/to/dir
is a file of type directory, and -prune
has not been called for it, then find
will carry on processing files within it, with path/to/dir/file-within
as the path being processed.
.
in that regard is just like any other path. It's a path to the current working directory, so if .
matches the criteria, that will print .
, and if the file-within
also does, it will print ./file-within
.
As others have said, the GNU implementation of find
has a -printf
predicate which can be used in place of -print
to print other attributes of the matching files than the file path. -print
is the equivalent of -printf '%p\n'
, but in place of %p
, you can also use:
%f
: the file's name without any leading path component (the basename or tail)%h
: the directory containing the file or .
for /-less path (the dirname or head).%P
: the file path relative to the initial dir/file it was found under, or the empty string for that file itself.%H
: the initial dir/file it was found underSo except for /
-less files, %p
is the same as %h/%f
and except for the initial dir/file themselves, the same as %H/%P
.
For example:
$ find path/to/dir other-file -printf '%%p="%p" %%h="%h" %%f="%f" %%H="%H" %%P="%P"\n'
%p="path/to/dir" %h="path/to" %f="dir" %H="path/to/dir" %P=""
%p="path/to/dir/file-within" %h="path/to/dir" %f="file-within" %H="path/to/dir" %P="file-within"
%p="other-file" %h="." %f="other-file" %H="other-file" %P=""
$ cd path/to/dir
$ find . -printf '%%p="%p" %%h="%h" %%f="%f" %%H="%H" %%P="%P"\n'
%p="." %h="." %f="." %H="." %P=""
%p="./file-within" %h="." %f="file-within" %H="." %P="file-within"
find . <criteria> -printf '%P\n'
Will report the paths of matching files relative to the current working directory but not for .
itself, so in the general case, you rather need:
find . <criteria> '(' -name . -print -o -printf '%P\n' ')'
(.
wouldn't match a -type f
criteria though, so we do not need to special case it in that case or any other case where we can guarantee .
won't match the criteria).
Now, assuming your echo
was just an example standing in for any another command, -printf
alone won't help.
With the GNU implementation of xargs
, and a shell with ksh-style process substitution, you can do though:
xargs -0I {} -a <(
find . <criterial> '(' -name . -print0 -o -printf '%P\0' ')'
) cmd -- {}
Where find
prints those relative paths NUL-delimited, which xargs
retrieves and passes as argument to cmd
.
Or even:
xargs -r0 -a <(
find . <criterial> '(' -name . -printf '.\0' -o -printf '%P\0' ')'
) cmd --
If cmd
can take more than one file path as argument (similar to what you can do with -exec cmd {} +
).
Now, that won't help if -exec cmd {} ';'
was intended to be used as a condition, like in:
find . -type f -exec grep -l needle {} ';' \
-exec cmd-for-files-with-needle {} ';'
Where the cmd-for-files-with-needle
is executed for files for which grep
succeeds (after having printed the path of the matching files with -l
).
Then, you could do instead (and that does not require GNU find
nor xargs
):
find . <criteria> -exec sh -c '
for file do
file=${file#./} # strip the leading ./ if any. It leaves . as is,
# so we do not need to special-case it
grep -l -- needle "$file" &&
cmd-for-files-with-needle -- "$file"
done' sh {} +
Note the need for --
, now that the file paths are not guaranteed to start with .
and therefore may start with -
.
Or you could use zsh where globs can do most of what find
can do. For instance:
for file in **/*(ND.m-1); do
grep -l -- needle $file &&
cmd-for-files-with-needle -- $file
done
Where the .
glob qualifier is the equivalent of find
's -type f
, m-1
of -mtime -1
, N
for nullglob, D
for dotglob.
Here, a ./
prefix is not included (you could do ./**/*
if you wanted it), .
is not included (like -mindepth 1
in GNU find
), and the list is sorted (and the sorting order can be changed with the o
/O
/n
glob qualifiers)
filename
orpath/to/sub/dir/filename
(without the leading./
)? – xhienne Dec 20 '16 at 17:30