How do I display file names that contain two characters and one of them is c?
With bash, set the glob settings so that missing matches don't trigger an error:
shopt -u failglob # avoid failure report (and discarding the whole line).
shopt -s nullglob # remove (erase) non-matching globs.
ls ?c c?
Question-mark is a glob character representing a single character. Since you want two-character filenames, one of them has to be a c
, and so it's either the first character or the last character.
With shopt -s dotglob
this would also surface a file named .c
.
If there are no matching files, setting these shell options causes all of the arguments to be removed, resulting in a bare ls
-- listing anything/everything by default.
Use this, instead:
shopt -s nullglob ## drop any missing globs
set -- ?c c? ## populate the $@ array with (any) matches
if [ $# -gt 0 ] ## if there are some, list them
ls -d "$@"
fi
I think the better way is to use find
:
find . -type f -name '*c*' -name '??'
That will search recursively. To list only files in the current directory:
find . ! -name . -prune -type f -name '*c*' -name '??'
If the files exist (and have no escaped characters like \c
), you can use a glob:
echo ?c c?
that will match files that have one character (?
) followed by a c
or that have a c
followed by one character (?
).
But will fail with file names that start with a dash like -n
or -e
or with backslash characters like \c
or \n
with echo
as both -e
and -n
are special to (some shells) echo
and \c
or \n
would be interpreted as an escape sequence (\c
ends output and \n
prints a new line, not the verbatim characters \n
in some shell implementation of echo and with bash when the option shopt -s xpg_echo
is set). Other applications or utilities (like ls
) will have some other options and may fail with many other dash-started or interpret other escape characters in file names.
Will also list a file named cc
twice.
If a file may start with a dash (like -n), use:
$ ls -d -- ?n -n
Or, better:
$ ls -d ./?n ./-n
Caveats
Glob match no file.
If the files do not exist, the glob will not be expanded and the glob will be printed in their original form.
$ echo ?m m? ?m m?
Except in zsh.
$ zsh -c 'echo ?m m?' zsh:1: no matches found: ./m?
The shell will exit with an error.
This behavior is controlled by Zsh'snomatch
option.$ zsh -c 'setopt +o nomatch; echo ?m m?' ?m m?
Or:
$ zsh -c 'echo ?m(N) m?(N)' ?m m?
But that will print
mm
twice.In bash you could get a similar result to zsh if the shell option
failglob
is set:$ bash -c 'shopt -s failglob; echo ?m m?' bash: no match: ?m
But the script will not stop, the shell will not exit. Well, technically, the line where the glob is used is not executed further but execution resumes on next script line.
In bash you could set the option
nullglob
to remove globs that don't match:$ bash -c 'shopt -s nullglob; echo ?m m? done' done
Using ls (similar with other programs)
With matching files there will be no problem, but will also match (and list) directories:
$ ls ?c c? bc cz sc ac: hjk
Beter use
-d
withls
(directories will be included but not expanded).$ ls -d ?c c? ac bc cz sc
Globs failing to match a file (or a directory)
Then
ls
will report a failure$ ls ?m m? ls: cannot access '?m': No such file or directory
Other programs might (probably) do not do similar checks.
Using
nullglob
with bash will give an empty list to ls, so, it will list the contents or the pwd as if onlyls
was executed:$ ls ?m m? ac a.out cz 3 b sc ab bc
That could be avoided using
./
$ ls -d ./?m ./m? ls: cannot access './?m': No such file or directory ls: cannot access './m?': No such file or directory
Or you can use a find regex (but it will traverse subdirectories with prune
missing) (Note that this will not repeat a file called cc):
$ find . ! -name . -prune -regex '.*/\(.c\|c.\)'
./ac
./cc
./sc
./bc
./cz