8

In a script I use find to collect some files in the current directory, as in

$ find . -name "*.h"
./foo.h

Now I'd like it to just output foo.h, without the ./ prefix. I thought that the empty string "" denoted the current directory in shell commands. But this gives:

$ find "" -name "*.h"
find: ftsopen: No such file or directory

So I was wrong. Now my question is when/how/where/.. does an "empty string (?)" denote the current dir in commands that expect a filename or a pathname? Is there a neat and enlightening explanation?

A side question is whether the find nitpicking above can be solved simply, without string manipulation a la ${parameter#word} or cut or sed?

  • 1
    find has the -printf parameter to manipulate how results are shown. find . -name '*.h' -printf '%P\n' will remove the ./ prefix. See what find . -name '*.h' -print does. – Valentin Bajrami Oct 11 '14 at 11:31
  • Thanks! Too bad my version of find does not support the -printf option. FWIW, strings on my find gives @(#)PROGRAM:find PROJECT:shell_cmds-175 ?!? – phs Oct 11 '14 at 13:12
  • 5
    @phs that's the kind of thing that makes it essential that you always mention your OS. – terdon Oct 11 '14 at 13:38
11

A long time ago (in 7th edition, 32V, 4.2BSD, 4.3BSD), at the system-call level a zero-length pathname denoted the current working directory (when used for lookup; it was disallowed when trying to create or delete a file or directory). In System III, it was an error to use a zero-length pathname under all circumstances, and the POSIX standard has this to say about pathname resolution:

A null pathname shall not be successfully resolved.

  • 3
    An empty string in $PATH, $MANPATH, $LD_LIBRARY_PATH (and others but not necessarily all $*PATH ones) denotes the current directory. dir/ is mostly the same as dir/. – Stéphane Chazelas Oct 12 '14 at 7:20
4

You can use:

find * -name "*.h"

Note that files in the current directory whose name starts with . will be omitted, and files whose name starts with - will be interpreted as options by find and cause havoc, so this is not a general equivalent to find . ….

The absence of of a string ending in "/" as part of a file name implies the current directory, but that doesn't mean the current directory is denoted by an empty string (which is arguably not the same as the absence of a string, although it might look the same when printed).

4

In general, the empty string does not denote the current directory, neither to shell commands nor in system calls. It did on some older systems, but not on POSIX-compliant systems.

Occasionally you'll find a program which uses the current directory when you pass an empty string and the program expects a directory name. This is sometimes deliberate, and sometimes a side-effect of prepending the current directory's absolute path when the given string does not start with a slash.

The best thing for you would be to leave the ./. It doesn't do any harm.

If the list of files is for

find . … | sed 's!^\./!!'

Note that this mangles some file names containing newlines. This usually isn't a problem for human consumption, and the output of find isn't suitable for program consumption since it is ambiguous. If you're using -print0, which is suitable for program consumption, you probably don't care about the ./ prefix anyway.

You can use find * … instead of find . …, but note that find * has a number of defects that make it unsuitable in general:

  • . is omitted.
  • All dot files (files whose name begins with . or ..`) are omitted.
  • If there is a file name in the current directory whose name begins with - (or a file called ! or (...), it will be interpreted as an option or predicate by find.

The first point doesn't matter if your filter excludes the current directory. For the second point, you can use the patterns ..?* .[!.]* * to match all files in the current directory, but you'll need to check whether each pattern matches at least one file and omit it if it doesn't. This is possible but very cumbersome. The last point is a stopper. So find * may be suitable for quickie command line use, but don't use it in a script.

An alternative approach is to use the shell's recursive globbing facility, e.g.

printf '%s\n' **/*.h

This needs to be activated by shopt -s globstar in bash and by set -o globstar in ksh93, and doesn't exist in a basic POSIX shell such as dash. Dot files will not be traversed by default; to include them, first make globbing not ignore dot files with shopt -s dotglob in bash or FIGNORE='@(.|..)' in ksh93. Also, if there are no matches, then this command prints the pattern; run shopt -s nullglob in bash to print an empty line instead, and use the pattern ~(N)**/*.h in ksh.

In zsh, recursive globbing is on by default. Use the glob qualifier D to include dot files and N to print an empty line if there are no matches (by default, zsh throws an error if a pattern doesn't match any file). You can use printf as above or

print -rl -- **/*.h(DN)
  • Thanks for all these tips. Most of the defects with find * are new to me, and some are frightening!! – phs Oct 12 '14 at 10:32
2

I cannot think of any example where the empty string denotes the current directory .. You may be thinking of invocations like ls, but that is because ls assumes the current directory if no parameter is given, and in fact it won't take the empty string:

ulmi@silberfisch:~$ ls ""
ls: cannot access : No such file or directory
  • 3
    One example where the empty string denotes the current directory is as a PATH component. – hvd Oct 11 '14 at 15:38
0

There are various tricks you can use to get find's output without the leading ./:

  1. Use find's -printf option and tell it to only print %f. See man find:

    %f                     File's name with any leading directories removed (only the last element).

    For example:

    find . -name "*.h" -printf "%f\n"
    
  2. Parse the output:

    find . -name "*.h" | sed 's#^./##'
    
  3. @Anthon's trick

    find * -name "*.h"
    

As for your other question, the empty string never denotes the current directory. It's just that various programs take the current directory as the default so when you run them with no arguments, the are run on the current directory.

  • That assumes GNU find. Or use %P to only strip the ./ – Stéphane Chazelas Oct 12 '14 at 7:25
  • 1
    find * -name '*.h' will find foo/.bar.h, but not .bar.h in the current directory. – Stéphane Chazelas Oct 12 '14 at 7:26
0

If the files are in the current directory you can just use ls:

$ ls *.sh

If you want the files in subdirectories also and just need the filename you could do something like:

$ find . -name '*.h' -exec basename {} \;

There are commands that will get the absence of a string as the current directory, but basically because they will get the current directory as default if nothing entered. The . always denote the current dir (and .. the parent directory)

  • the OP only wanted ./ removed, while your find solution removes all directory components – artm Oct 11 '14 at 15:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.