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 -cSo 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)
filenameorpath/to/sub/dir/filename(without the leading./)? – xhienne Dec 20 '16 at 17:30