2

I'm trying to list all video directories (not files) which don't end in a year e.g. (2015) and more specifically which don't have a year listed, therefore don't end in a closing bracket ).

How can I do the opposite of the following:

# List all files and directories end with '(2015)'
ls -ld *\(2015\)

# List all files and directories end with ')'
ls -ld *\)

Can anyone help explain how to do this?

4

Try this:

ls -ld *[^\)]

[] - set of characters that we are interested in

^ - negation (so we include set of characters that we don't want)

\) - escaped ')'

  • @PaulCee: This will include non-directories too. – cuonglm Feb 17 '16 at 15:43
  • 1
    Since this is the accepted answer, I'll add that to fix the problem @cuonglm pointed out, one should use ls -ldF *[^\)]/. – gardenhead Feb 17 '16 at 16:11
2

In bash or zsh:

for i in *; do [ -d "$i" ] && [[ ! "$i" =~ \)$ ]] && printf "%s\n" "$i"; done

That will simply cycle over all files and directories in the current directory and check that i) they're a directory ([ -d "$i" ]) and ii) their name doesn't end with a ) ([[ ! "$i" =~ \)$ ]]. items passing both tests will be printed.

This can deal with arbitrary file names (which ls can't).

  • @BinaryZebra both of those simply print all directories. – terdon Feb 18 '16 at 10:29
  • Maybe: (no [ -d ] test)?: set -- *[!\)]/; for i; do printf "%s " "${i%/}";done; echo – user79743 Feb 18 '16 at 10:38
  • @BinaryZebra yes, that one should work on bourne-type shells. – terdon Feb 18 '16 at 11:03
2

Method 1a:

ls -ld -- *[^\)]

(using the […] pathname expansion (wildcard/glob) pattern: [abc] matches a, b, or c; [^abc] matches anything except a, b, or c; so [^\)] matches anything except ).)

Method 2a:

GLOBIGNORE="*)"
ls -ld -- *

Pretty self-explanatory?

Both of these methods will match files and other objects that are not directories.


Updated with don_crissti's suggestion, to find directories only:

Method 1b:

ls -ld -- *[^\)]/

Method 2b:

GLOBIGNORE="*)/"
ls -ld -- */

Note that you must include the / in the GLOBIGNORE value.

2

Your command ls -ld *\(2015\) should be divided into two parts:

  • The filename globbing is done by the shell. Thus, different shells may work differently.
  • The ls command prints a long line -l for each of the filenames listed by the expansion done by the shell. Avoiding going inside directories with -d.

Shell

In general, this will work with all shells to produce all directories (with an added trailing /):

echo */                           ### disregarding effects of `-` and `\`

And to produce all directories that do not match an ending ):

echo !(*\))/
printf '%s ' !(*\))/ ### avoid interpretation of \.
echo -E ^*\)/

With extended globbing active for bash and zsh (ksh use it by default).

shopt -p extglob            ### for bash (on by default in interactive use).
setopt extendedglob         ### for zsh

Remove Trailing /

set -- */
printf '%s ' "${@%/}"

Or for bash and ksh (for example):

set -- !(*\))/
printf '%s ' "${@%/}"

zsh offers a simpler way to remove trailing \.

[tag:zsh]`echo -E ^*\)(/)`

About [!\)]

Notice that above I have omitted (on purpose) the use of [ ].

The use of [!\)] may seem equivalent (for one character):

echo !(*\))/
echo *[!\)]/

But the square brackets do not work for an string. This are not equivalent:

echo !(*\(2015\))/
echo *[!\(2015\)]/

However, it must be noted that echo *[!\)]/ works for POSIX shells.

ls -ld -- *[!\)]/                               ### POSIXly.

Join ls and */

Depending on the shell, and if a trailing / is a problem or not.
The two parts ls and shell put together might be:

ls -ld -- !(*\(2015\))/              ### An example for bash and GNU ls.

Alternatives

The (GNU) command ls itself include a way to avoid patterns:

ls -l --ignore='*)'

Bash offers GLOBIGNORE:

GLOBIGNORE='*)/'; echo */

Maybe find?

find . -maxdepth 1 ! -name . -prune -type d ! -name '*2015)'

But find seems like overkill to me for this simple need.

1

POSIXly:

find . ! -name . -prune -type d ! -name '*)'

With zsh:

print -rl -- *[^\)](/)

Other Bourne-like shells don't have ability to filter globbing result. You can use:

printf '%s\n' *[!\)]/

to match directories only. But it will add a trailing slash to your expansion result. With directory foo, you will get foo/.

  • Doesn't bash with extended globbing support negations like ls -d !(*\))/? – steeldriver Feb 17 '16 at 15:16
  • @steeldriver: the (/) qualifier in zsh is different with / in ls -d !(*\))/. And you don't need extended glob. – cuonglm Feb 17 '16 at 15:21
  • find . ! -name . -prune -type d ! -name '*)' simply worked for my purpose, thank you. – Paul Cee Feb 17 '16 at 15:33
  • A square bracket match only one character, will not work for (2015). – user79743 Feb 18 '16 at 9:21
  • @BinaryZebra: how can't it work? It's not relevant to the OP question – cuonglm Feb 18 '16 at 9:50
0

Using bash, you can use GLOBIGNORE:

$ ls -d1 *
excludeme(2015)
excludmetoo(2016)
somedir

$ GLOBIGNORE='*)'
$ ls -d1 *
somedir

GLOBIGNORE:

A colon-separated list of patterns defining the set of filenames to be ignored by filename expansion. If a filename matched by a filename expansion pattern also matches one of the patterns in GLOBIGNORE, it is removed from the list of matches.

  • That will also match files.. – terdon Feb 17 '16 at 15:10
0

I think find is quite appropriate for this

find . -type d ! -regex '^.*)' -ls

-regex '^.*)' matches all names ending with a bracket, ! inverts that, -type d option is for dirs, -ls (optionally) makes output look ls-like

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.