1

Context: OSX Catalina (zSh) and Ubuntu 16.04 (Bash). OSX terminal commands:

FILES="$PWD/*"  #list of files
echo $FILES

returns a string of all files to the console. For now, it is my intent to seed a copy of the script in the directory to be processed. In the future, I expect enable the script to accept an argument (directory) to be processed.

Assume file types (extensions and not hidden files) are present in the working directory:

  • .abc
  • .ABC
  • .def
  • .ghi
  • .jkl
  • .sh

I am interested in modified examples of the above instructions that will:

  1. explicitly select only .abc .ABC & .def file types (extensions)
  2. select all files except .sh & .jkl

Ideally there would be a command option to specify case-insensitive

gatorback
  • 1,384
  • 23
  • 48
  • If I'm not mistaken, macOS has now moved to zsh as its default shell. I have therefore tagged your question with bash (for Ubuntu) and zsh. – terdon Oct 03 '20 at 12:53
  • @terdon. Yes: I have verified the zsh is the Catalina default shell: echo $SHELL ` – gatorback Oct 03 '20 at 17:32
  • 1
    zsh is now the default interactive shell on macOS, but scripts can run under any shell. Just set the shebang appropriately (e.g. #!/bin/bash) and don't override it by running the script with a command like sh scriptname. – Gordon Davisson Oct 03 '20 at 17:40

2 Answers2

4

You can do this quite easily using arrays in bash (which is the default shell on Ubuntu, macOS's is either bash or zsh for newer versions; these solutions are about bash):

  1. Select all files (your original command, slightly modified):

     $ shopt -s nullglob
     $ files=(*)
     $ echo "${files[@]}"
     file.abc file.ABC file.def file.ghi file.jkl file.sh
    

    Turning on the nullglob option gives an empty list if the directory is empty. Without this option, if the directory was empty, files would be a list of one element which is the unexpanded *.

  2. Select only those files whose names end in .abc or .ABC or .def:

    $ shopt -s nullglob
    $ files=(*.abc *.ABC *.def)
    $ echo "${files[@]}"
    file.abc file.ABC file.def
    
  3. Select all files except those whose names end in .sh or .jkl

    $ shopt -s extglob nullglob
    $ files=(!(*.sh|*.jkl))
    $ echo "${files[@]}"
    file.abc file.ABC file.def file.ghi
    

  4. Case insensitive matching

    $ shopt -s nocaseglob nullglob
    $ files=(*abc)
    $ echo "${files[@]}"
    file.abc file.ABC
    

Also, a note on what you were doing. files="$PWD/*" does not set the value of $files to all files in your current directory. It sets its value to the a string ending in /*:

$ cd /tmp/current_directory
$ files="$PWD/*" 
$ echo "$files"
/tmp/current_directory/*

The reason you thought it worked, was because you were using echo $files unquoted, which means that the shell was expanding /tmp/current_directory/*. If, however, you were to then delete the files, the same echo command would simply print /tmp/current_directory/* with $PWD expanded to whatever directory you had originally run this in. The list of files would not be saved with the variable. Using the arrays as I did here ensures that the variable stores the actual list of files and not just the glob that can be expanded to different values depending on where you use it.

terdon
  • 242,166
  • I invite you to review and answer or comment a follow-on question. https://unix.stackexchange.com/q/612808/182280 – gatorback Oct 04 '20 at 04:51
3

In zsh:

files=($PWD/*.(abc|ABC|DEF)(N))
print -rC1 -- $files # print raw, on one column.

(with the (N) qualifier to apply nullglob to that glob, to that $files become an empty list of the pattern doesn't match any file instead of reporting an error).

For files other than .sh and .jkl:

set -o extendedglob # needed for the ^ negation operator
files=($PWD/^*.(sh|jkl)(N))

For case insensitive matching (.ABC/.abc/.Abc...):

set -o extendedglob
files=($PWD/*.(#i)abc(N))

Your:

FILES="$PWD/*"
echo $FILES

is wrong on several accounts:

FILES="$PWD/*" doesn't store the list of files in the $FILE variable. That's a scalar assignment, which can only ever store one value. Instead it stores in $FILES the contents of $PWD followed by /* literally.

In, echo $FILES as $FILES is not quoted, in bash (but not zsh), the expansion of $FILES is subject to split+glob. And it's at that point, and assuming that $FILES contains none of the characters of $IFS (which would trigger the split part), and that $PWD doesn't contain wildcard characters (that would also trigger the glob part) that the value is expanded to the list of matching files.

In zsh, split+glob is not done implicitly upon parameter expansion, you need to request them explicitly ($=FILES for splitting, $~FILES for globbing, $=~FILES for both).

Then using echo to output arbitrary data is wrong as echo does extra processing by default (in zsh, you could use echo -E - $files or print -r -- $files though).