I've got a directory that looks like

$ ls
Broad_hapmap3_r2_Affy6_cels_excluded.tgz  DINGO.tgz                     GIGAS.tgz  index.html          IONIC.tgz             passing_cels_sample_map.txt  SCALE.tgz
CHEAP.tgz                                 EPODE.tgz                     HOMOS.tgz  index.html?C=M;O=A  LOVED.tgz             PICUL.tgz                    SHELF.tgz
CORER.tgz                                 excluded_cels_md5.txt         HUFFS.tgz  index.html?C=N;O=D  NIGHS.tgz             POSIT.tgz                    SLOTH.tgz
CUPID.tgz                                 excluded_cels_sample_map.txt  HUSKS.tgz  index.html?C=S;O=A  passing_cels_md5.txt  SAKES.tgz                    TESLA.tgz

I want to unzip all the files that match the extension *.tgz with a single command, except Broad_hapmap3_r2_Affy6_cels_excluded.tgz.

I can do

ls *.tgz | xargs -n1 tar zxvf

for all the *tgz files, but what's a good way to exclude a subset of them? From reading online maybe find is indicated in this situation, but it seems like overkill. Thank in advance.

Addendum: I'd also be interested in alternative methods to the question without excluding files.

  • Thanks to everyone for their answers. I'm sorry that I can only accept one. :-) – Faheem Mitha Feb 16 '11 at 18:16

You could always do:

ls *.tgz | grep -v Broad_hapmap3_r2_Affy6_cels_excluded.tgz | xargs -n1 tar zxvf

But I suspect somone will post a cleaner way do do this directly from the bash shell without needing a grep in there.

  • In general, you want to avoid parsing the output of ls when possible. – Steven D Feb 16 '11 at 19:11
  • @Steven: Sorry, don't follow. Can you elaborate? – Faheem Mitha Feb 16 '11 at 19:20
  • 2
    When you parse the output of ls you can run into a lot of problems if you encounter filenames with special characters. See the following for a better explanation: mywiki.wooledge.org/ParsingLs – Steven D Feb 16 '11 at 19:27
  • @Steven: Thanks for the link. The referenced article mentions newlines, but now that I think about it, in Windows world for example, they like filenames with spaces in them, and try to export it elsewhere, and that would by itself mess up the oneliner here, I think. What would you prefer as an alternative? find? – Faheem Mitha Feb 16 '11 at 19:32
  • I would use find for certain complicated cases and glenn jackman's answer in most easy cases. I wouldn't be surprised if there was some magical shell globbing incantation possible with ZSH or Bash 4. – Steven D Feb 16 '11 at 19:41

In zsh:

setopt extended_glob
for z in *.tgz~Broad_hapmap3_r2_Affy6_cels_excluded.tgz; do tar xzf $z; done

Another possibility in zsh:

setopt extended_glob
zargs -n 1 -- *.tgz~Broad_hapmap3_r2_Affy6_cels_excluded.tgz -- tar xzf

In ksh (but not bash or zsh as they don't support and patterns):

for z in @(*.tgz&!(Broad_hapmap3_r2_Affy6_cels_excluded.tgz); do tar xzf "$z"; done

Another way in ksh:

( FIGNORE=Broad_hapmap3_r2_Affy6_cels_excluded.tgz;
  for z in *.tgz; do tar xzf "$z"; done )

In bash:

( GLOB_IGNORE=Broad_hapmap3_r2_Affy6_cels_excluded.tgz;
  for z in *.tgz; do tar xzf "$z"; done )

In any shell:

for z in *.tgz; do
  [ "$z" = Broad_hapmap3_r2_Affy6_cels_excluded.tgz ] || tar xzf "$z"

Yet another hackish way:

mv Broad_hapmap3_r2_Affy6_cels_excluded.tgz Broad_hapmap3_r2_Affy6_cels_excluded.tgz.not
for z in *.tgz; do tar xzf "$z"; done
mv Broad_hapmap3_r2_Affy6_cels_excluded.tgz.not Broad_hapmap3_r2_Affy6_cels_excluded.tgz

If you also want to act on files in subdirectories, find is the natural tool to turn to. The recursive examples below exclude files named Broad_hapmap3_r2_Affy6_cels_excluded.tgz in any subdirectory.

find . -name '*.tgz' -type f \
       \! -name Broad_hapmap3_r2_Affy6_cels_excluded.tgz \
       -exec tar xzf {} \;

For a recursive traversal in zsh or bash ≥4, you can use ** in patterns. In zsh:

setopt extended_glob
zargs -n 1 -- **/*.tgz~**/Broad_hapmap3_r2_Affy6_cels_excluded.tgz -- tar xzf

In bash ≥4:

shopt -s globstar
( GLOB_IGNORE=Broad_hapmap3_r2_Affy6_cels_excluded.tgz;
  for z in **/*.tgz; do tar xzf "$z"; done )

You can even use find if you don't want a recursive traversal, though it's not the most convenient way then.

find *.tgz  -type f \
            \! -name Broad_hapmap3_r2_Affy6_cels_excluded.tgz \
            -exec tar xzf {} \;

For one-off things like this, I'd just brute-force it:

for f in *.tgz; do 
   [[ "$f" != "Broad_hapmap3_r2_Affy6_cels_excluded.tgz" ]] && tar zxvf "$f"

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.