14

I have the followin directory structure:

base/
   files/
   archives/
   scripts/

I want a script to run from scripts/, compress files that match results.*.log in files/ into a gzipped tar archive in archives/.

I'm trying the following command:

tar czfC ../archives/archive.tar.gz ../files results.*.log

But I get

tar: results.*.log: Cannot stat: No such file or directory
tar: Exiting with failure status due to previous errors

While

tar czfC ../archives/archive.tar.gz ../files results.a.log

works as expected. Also

tar czf ../archives/archive.tar.gz ../files/results.*.log

works the way I would like, except it adds the prefix files/ to the file and also emits a warning:

tar: Removing leading `../' from member names

So my conclusion is that tar globbing doesn't work properly when using the -C option. Any advice on how I make this work in a simple manner?

7 Answers 7

6

Write it the more portable way:

(cd ../files && tar cf - results.*.log) | 
  gzip -9 > ../archives/archive.tar.gz
3
  • The glob will still be interpreted in the current directory, before the cd happens.
    – vonbrand
    Apr 6, 2013 at 16:50
  • 1
    @vonbrand, no, of course it won't. && is like ; or newline, it separates two commands. It's like saying that after running cd, any glob would expand files in the previous directory. Apr 6, 2013 at 19:35
  • You are right. One never finishes learning.
    – vonbrand
    Apr 6, 2013 at 22:54
6

There is no "tar globbing", the globbing is done by the shell. And the shell has no idea that tar -C somedir will do its work in somedir, any globs are expanded in the current directory.

2
  • Indeed, if results.a.log exists in the directory from which the script is run, it works. But I don't understand why this happens, since -C stands for change directory, so the current directory should be the one with results.a.log. I can't accept your answer because you don't offer a solution.
    – Yuval
    Apr 6, 2013 at 14:35
  • 3
    The shell takes the command, expands globs in the current directory, manufactures the arguments from the above, and launches the tar program, which changes directory much later. Can't "solve" your problem, sorry. The only way out is to move into the directory where you want to glob, or do the globbing in the directory and save its results in a file or variable.
    – vonbrand
    Apr 6, 2013 at 14:46
1

I finally used

tar czfC ../archives/archive.tar.gz ../files `find ../files/results.*.log -printf "%f\n"`
1
  • While that may work for you, it's a pretty complex way to do it. Furthermore, it won't work with file names containing special characters such as whitespace. Use one of the sane methods that have been proposed in answers. Apr 8, 2013 at 1:16
1

You can let find do the globbing and omit the 'change directory' option to tar by removing the files/ prefix before creating the tarfile.

The --no-recursion option to tar is optional since no directories are going to be archived.

cd base/scripts

# gnutar
# printf '%s\000' ../files/results.*.log |  # find alternative
find ../files -maxdepth 1 -type f -name "results.*.log" -print0 | 
   gnutar --transform="s|^\.\{0,1\}\.\{0,1\}/\{0,1\}files/||" -cf - --null --no-recursion -T - | 
   gzip -9 > ../archives/archive.tar.gz
gnutar -tf ../archives/archive.tar.gz | nl
gnutar -xzf ../archives/archive.tar.gz

# bsdtar
find ../files -maxdepth 1 -type f -name "results.*.log" -print0 | 
   bsdtar -s "|^\.\{0,1\}\.\{0,1\}/\{0,1\}files/||" -cf - --null --no-recursion -T - | 
   gzip -9 > ../archives/archive.tar.gz
bsdtar -tf ../archives/archive.tar.gz | nl
bsdtar -xzf ../archives/archive.tar.gz
0

The way to do that is to write something like the following:

#!/bin/sh

cd ../files
tar zcf ../archives/archive.tar.gz results.*.log
cd ../scripts
0

It's not tar that does the globbing, it's the shell. You're first telling the shell to do globbing, then telling tar to change directories. Since tar is receiving a list of file names relative to the original directories, this doesn't work.

The easiest solution by far is to let the shell change the directory. Interactively:

cd ../files
tar czf ../archives/archive.tar.gz  results.*.log
cd -

In a script:

(
  cd ../files &&
    tar czf ../archives/archive.tar.gz  results.*.log
)

or

(
  cd ../files &&
    tar czf -  results.*.log
) >../archives/archive.tar.gz
0

TL;DR (solution without cd or null bytes)

tar -czf ../archives/archive.tar.gz -C ../files $(find ../files -maxdepth 1 -type f -name "results.*.log" -printf "%f\n")

or

find ../files -maxdepth 1 -type f -name "results.*.log" -printf "%f\n" | tar  -czf ../archives/archive.tar.gz -C ../files -T -

Note: if you want to work with file names containing special characters such as whitespace us the SuperUser answer below using null bytes that won't be embeddable in most programming languages.

What does not work

There is a limitation to the tar directory option

       -C, --directory=DIR
              Change to DIR before performing any operations.  This option is order-sensitive, i.e. it affects all options that follow.

The issue with the following command is that the shell globing in results.*.log is expended before the tar option is applied, so in the current directory.

tar czf ../archives/archive.tar.gz -C ../files results.*.log

The below command works but it includes all files in ../files and not only the wanted logs.

tar czf ../archives/archive.tar.gz -C ../files .

The below command works but will store the relative path and not just the filename as we want.

tar czf ../archives/archive.tar.gz -C ../files ../files/results.*.log

The following command from this SuperUser answer (How do I remove the full path when doing a tar.gz compression?) works of executed from a shell directly but the issue is that you can't execute that in programming languages because most of them won't allow a null byte in a string (eg. string contains null byte (ArgumentError) in Ruby).

find ../files -maxdepth 1 -type f -name "results.*.log" -printf "%P\0" | tar --null --remove-files -czf ../archives/archive.tar.gz -C ../files -T -

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .