51
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?

1
  • 2
    For files in sub-directories, do you just want filename or path/to/sub/dir/filename (without the leading ./)?
    – xhienne
    Dec 20, 2016 at 17:30

6 Answers 6

67

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:

  • hidden files are omitted at the top level but not at deeper levels (the ones traversed by find).
  • the list is sorted at the top level (generally a plus)
  • you may run into the limit on the number of arguments that can be passed to command if the current working directory has a lot of file.
  • That won't work if there are files named !, (, ) or with a name starting with -, with possible nasty consequences if there's a file called -delete for instance.
8
  • works on macOS Catalina zsh
    – Gi0rgi0s
    Aug 20, 2020 at 21:43
  • 7
    This is neat. It's worth mentioning that * 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, 2020 at 22:45
  • 8
    @Hannes Don't use .* 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 2, 2021 at 9:20
  • You are right. I upvoted your answer.
    – Hannes
    Apr 6, 2021 at 1:49
  • 2
    With this I get shellcheck issue SC2012 - just imagine one of the files is named -name.
    – Johannes
    Aug 8, 2022 at 20:58
38

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.

6
  • Using the .[^.]* syntax does not work in any (posix) shell that I am aware of.
    – Juan
    Jul 12, 2021 at 15:19
  • @Juan From what I read, you know at least one shell that does not understand the .[^.]* 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, 2021 at 11:33
  • some shells where the .[^.]* 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': pubs.opengroup.org/onlinepubs/9699919799/utilities/…). So the shells where it does not work appear to be deficient as far as POSIX is concerned.
    – Juan
    Aug 20, 2021 at 14:40
  • Regarding the previous comment. Interestingly, in the posix doc specified in the previous comment, section 2.13.1 explains that '!' can be used for the character class negation. And using '^' unquoted can produce "unspecified results". If you do 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, 2021 at 15:35
  • It looks like using .[!.]* 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, 2021 at 15:42
7

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.

3
  • The cut option is the only one that works on my Linux machine for multilevel outputs. Thanks!
    – GenesRus
    Mar 18, 2021 at 20:43
  • 3
    The form that omits specifying a search path/paths (find -type f) is not currently POSIX compatible behavior. This fails with variants of find(1) that are not GNU find.
    – Juan
    Jul 12, 2021 at 15:22
  • 2
    cut cuts each line but there's nothing stopping a file path from containing newline characters. Jul 5, 2023 at 16:19
6

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

3
  • the question was to eliminate leading "./" and not pathnames, but thanks for the trigger to read into find's printf capabilities. BTW: '%h/%f' does not help either.
    – grenix
    May 12, 2021 at 20:27
  • 2
    @grenix Try: find . -type f -printf '%P\n'
    – pallgeuer
    Aug 9, 2022 at 9:38
  • From man page > %f File's name with any leading directories removed (only the last element). Jul 5, 2023 at 15:40
1

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:

  • We rely on the fact that sh -c 'echo $0' foobar runs a subshell which prints $0, and foobar is passed as $0, so it prints foobar
  • instead of $0, we actually use ${0#./}, which means "use $0, but strip leading ./ (#./) - this is parameter expansion
  • we pass {} 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#./}' "{}" \;
0
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 under
  • and many other metadata of the file.

So 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)

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .