I tried doing ls [a-z][a-z]
, but it doesn't seem to be working.
4 Answers
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
-
-
erm, not quite -- failglob removes the error but also removes everything (thus listing everything) if there are no matches– Jeff Schaller ♦Oct 5, 2018 at 17:29
-
-
2We'll need clarification on whether a file named
cc
consists of "one" c :)– Jeff Schaller ♦Oct 5, 2018 at 19:24
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 '??'
-
2with the caveat about discovering subdirectories (
-maxdepth 1
would help for GNU find)– Jeff Schaller ♦Oct 5, 2018 at 19:29 -
@ Jeff Schaller here too We'll need clarification on whether we search the file in the courrant directory or anywhere :)– ctac_Oct 5, 2018 at 19:43
-
gnu ls have a option --hide=PATTERN so I try ls *c* --hide='?' --hide='???*' but it's buggy or I don't know how to use it– ctac_Oct 5, 2018 at 19:59
-
1It works just like
-I
... it's not buggy, it only works if you don't specify a pattern forls
(that's what they mean byimplied entries
- whateverls
would print by default... if you runls *c* ...
that's no longer implicit, it's explicit...) Oct 5, 2018 at 20:07 -
1With hide, this works:
ls --hide='?' --hide='???*' --hide='[^c][^c]'
.– user232326Oct 7, 2018 at 2:45
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
-
bash
exits inbash -c 'shopt -s failglob; echo ?m m?; echo not reached'
. Note that thenomatch
zsh option name comes from csh/tcsh. The behaviour is slightly different though between csh and tcsh. See thecshnullglob
option inzsh
to emulate csh's (or pre-Bourne shells using /etc/glob) behaviour.fish
now also behaves likezsh
/bash -o failglob
except that failing matches don't exit the shell. Oct 6, 2018 at 22:35 -
Exit? Only if both commands are in one line, try
bash -c $'shopt -s failglob; echo ?m m?\n echo not reached'
. Or (I don't recomend this)bash -c eval\ $'shopt -s failglob; echo ?m m?\n echo not reached'
. I tested it inside an script, one command per line. @StéphaneChazelas– user232326Oct 6, 2018 at 23:02 -
Hmmm, the rest seems to be too much information for one answer IMhO. As the answer is tagged bash, I believe that I will pass adding that. @StéphaneChazelas– user232326Oct 6, 2018 at 23:04
-
Technically,
failglob
in an interactive shell will bring you back to the prompt (unless the failed glob happened in a subshell). And it's the same in scripts, it resumes execution at the next line the shell would read. So for instance, if the failing glob happens in a function and not in a subshell there, that aborts the function and whatever other commands that were connected. It's a bit messed up. (bash also doesn't have any exception catching like zsh'salways {...}
). Oct 6, 2018 at 23:16 -
Technically, don't you think that it is useful to other users to say that bash will not (in all cases) stop executing an script. zsh stops in all cases. @StéphaneChazelas– user232326Oct 6, 2018 at 23:51
In bash
:
Either
c
followed by a character (?c
) or a character (including.
withdotglob
) followed byc
(?c
).shopt -s extglob failglob dotglob printf '%s\n' @(?c|c?)
All files but those that either don't contain
c
(!(*c*)
) or are not two characters (!(??)
). Here using a double-negation to achieve conjunction.shopt -s extglob failglob dotglob printf '%s\n' !(!(*c*)|!(??))
In zsh
:
printf '%s\n' (?c|c?)(D)
(where
D
enablesdotglob
;failglob
(callednomatch
inzsh
) is on by default there).double negation using the
~
except operator and^
negation extended operators:set -o extendedglob printf '%s\n' *c*~^??(D)
In ksh93
:
there's no
nomatch
/failglob
option in ksh93, but you can do it by hand there:FIGNORE='@(.|..)' # only ignore . and .. and not the other dotfiles files=(~(N)@(?c|c?)) if ((${#files[@]})); then printf '%s\n' "${files[@]}" else echo >&2 No match exit 1 fi
(where
~(N)
enablesnullglob
for that glob).ksh93
does have a conjunction operator. So here, you can also change thefiles=(...)
line to:files=(~(N)@(*c*&??))
-
Maybe add a note for Nullglob as this:
zsh -c 'printf "%s\n" ?m(N) m?(N) yes'
– user232326Oct 6, 2018 at 23:07 -
Nitpicking: What the original poster (OP) posted:
ls *
prints results in one line. Your `printf %s\n`` (in all your options) prints one line per result.– user232326Oct 6, 2018 at 23:12 -
@Isaac, ++Nitpicking,
ls *
prints one file per line unless stdout is a tty device in which case the format is "implementation-defined" per POSIX and in practice in most implementations, printed in columns. Oct 6, 2018 at 23:20 -
@isaac,
zsh -c 'printf "%s\n" ?m(N) m?(N) yes'
would printmm
twice (and may also not give a sorted list).zsh -c 'printf "%s\n" (?c|c?)(N)'
would print an empty line if there was no matching file. To print no output when there's no matching file:(){ (($#)) && printf "%s\n" $@; } (m?|?m)(N)
or() { printf %s $^@$'\n'; } (m?|?m)(N)
. It's actually annoying that there's no simpler command just to print a list of lines given as argument (you'd expectprint -rl
to do that but it doesn't). Oct 6, 2018 at 23:27 -
It would be useful both for ls and print (and probably many other utilities) to have an option of
-e
(for empty) to be used in scripts that will avoid doing anything on an empty list of arguments.– user232326Oct 6, 2018 at 23:42