3

I'm trying to include files that match */*/.thisdirectory/* in a tar archive. What's the correct syntax? Apparently this is not:

> find .
.
./bar
./bar/.a
./bar/.a/baz
./foo
./foo/.a
> tar --include "*[.]a*" -cvf a.tar .
>

3 Answers 3

1

I'm assuming you're using FreeBSD tar¹ as this is the only implementation I know with a --include option.

When you pass --include, only explicitly included files are traversed. When building an archive, if a directory isn't traversed then neither it nor any of its contents can be included. Since you're tarring . but not including ., the sole command line argument doesn't get included, and you get an empty archive.

To reach the .a directories, you would need to include all the parent directories. The thing is, if you do that, then all of their contents will be included as well. You can use --exclude to exclude them, but then you need an exclude pattern that doesn't exclude the dot directories that you want to include. At this point --include is no longer of any use. The --include option is rarely useful when creating an archive, it's rather more useful when copying part of an existing archive (because when doing that, there's no recursive traversal, just iteration through the list of members of the input archive).

tar --exclude='./*/[!.]*' -cvf a.tar .

Note that all files in the toplevel directory are included. I don't think there's a way to include only those directories that will have non-empty contents.

To get only the ./*/.* directories and nothing else (in particular not their parent directories), you need to pass them as arguments to tar, rather than let tar recurse to find them.

tar -cvf a.tar ./*/.[!.]*/

Remove the trailing slash to include files that aren't directories (or symlinks to directories). Add ./*/..?*/ to include directories whose name begins with .. if you have those.

¹ Possibly under a different operating system.

1

If your shell is bash, you can use the GLOBIGNORE variable to hide the . and .. directories. This does automatically also set the dotglob option, so * now matches both hidden and non-hidden files:

GLOBIGNORE=".:.."
tar czf tarball-with-hidden-files.tgz directory/*

To expand on this a bit: by default, * does not match hidden files (files starting with a .), which includes the current (.) and parent (..) directories of any directory. If you want a glob (e. g. a command line wildcard) to match them, you need to set the dotglob option, but this can cause really bad things to happen if .. matches * and you do something like rm -fr /home/olduser/* (because olduser/.. matches, which is /home/ and now you've just fried everyones' home directories. Which is where GLOBIGNORE comes into play- it blacklists things from matching wildcards. As a really simple use-case:

ghoti@home:~/scratch$ ls -ld ?dotfile
ls: cannot access '?dotfile': No such file or directory
ghoti@home:~/scratch$ touch _dotfile .dotfile
ghoti@home:~/scratch$ ls -ld ?dotfile
-rw-rw-r-- 1 ghoti ghoti 0 May  6 16:58 _dotfile
ghoti@home:~/scratch$ GLOBIGNORE=".:.."
ghoti@home:~/scratch$ ls -ld ?dotfile
-rw-rw-r-- 1 ghoti ghoti 0 May  6 16:58 _dotfile
-rw-rw-r-- 1 ghoti ghoti 0 May  6 16:58 .dotfile

What does this mean for you? Well, as in the quick example at the top of this answer, once you set GLOBIGNORE to ignore . and .., you can use * to match hidden directories like the ones you're trying to archive:

tar czf mytarball.tgz directory/*  # this will include things like directory/.hiddenfile

To point this at your specific example of a directory called .a:

ghoti@home:~/scratch$ mkdir -p example example/foo/.a example/bar/.a example/foo/_a example/bar/_a
ghoti@home:~/scratch$ touch example/{foo,bar}/{.,_}a/file
ghoti@home:~/scratch$ find example/
example/
example/foo
example/foo/_a
example/foo/_a/file
example/foo/.a
example/foo/.a/file
example/bar
example/bar/_a
example/bar/_a/file
example/bar/.a
example/bar/.a/file
ghoti@home:~/scratch$ GLOBIGNORE=".:.."
ghoti@home:~/scratch$ tar cvzf withdotfiles.tgz example/*/?a
example/bar/_a/
example/bar/_a/file
example/bar/.a/
example/bar/.a/file
example/foo/_a/
example/foo/_a/file
example/foo/.a/
example/foo/.a/file
ghoti@home:~/scratch$ unset GLOBIGNORE
ghoti@home:~/scratch$ tar cvzf withoutdotfiles.tgz example/*/?a
example/bar/_a/
example/bar/_a/file
example/foo/_a/
example/foo/_a/file
9
  • I must be missing something, because that doesn't improve the situation at all: > GLOBIGNORE=".:.." tar --include "*[.]a*" -cvf a.tar . > The .* directories get included just fine without --include but trying to restrict what gets tarred fails utterly...
    – cbmanica
    May 6, 2016 at 23:53
  • @cbmanica the command you show in no way resembles DopeGhoti's example nor does it */*/.thisdirectory/* so clarifications are needed on what files you want to include. May 6, 2016 at 23:58
  • I thought it was extremely clear that I want to include files that match ".foo" from a current directory. If my command didn't match that, maybe that's because I at no time indicated I was dealing with a tgz.
    – cbmanica
    May 7, 2016 at 0:01
  • I suppose given that find . | grep "[.]thisdirectory" > files; tar -cf my.tar -T files does what I want, this is all academic, but I get tired of SO being so willfully pedantic in response to all questions.
    – cbmanica
    May 7, 2016 at 0:02
  • Without specific questions, it's exceedingly difficult to give specific answers.
    – DopeGhoti
    May 7, 2016 at 0:06
0

Rather jumping on an old horse here, but, since I can't find any evidence of anyone pointing out the apparently "intended" way that this should be done with tar:

The issue, as has been noted elsewhere, is that tar is quite literal with "--include", and it won't traverse into directories foo and bar to find your .a files, if the --include filters them off first.

The clue to the trick to using tar as you'd like to use it /is/ included in the man page, but it's fairly well obfuscated: --include is "supposed" to be used to filter existing archives; Also, there is a magic argument @- that causes tar to treat a read from STDIN as a read from an existing archive.

Put together, you can use one promiscuous tar statement to create an archive of everything below the current directory, and stuff it onto STDOUT. You then pipe this into a tar that's going to use the --include option, and that reads from STDIN.

In your case, this looks something like:

tar -cf - / | tar -cf myfiles.tar --include=".a" @-

Soyokaze:test$ ls -lR
total 32
drwxr-xr-x   6 tyler  staff   204 Nov 29 14:24 bar/
drwxr-xr-x   4 tyler  staff   136 Nov 29 14:23 foo/

./bar:
total 0
-rw-r--r--  1 tyler  staff    0 Nov 29 14:23 baa.a
-rw-r--r--  1 tyler  staff    0 Nov 29 14:23 baaa.a
-rw-r--r--  1 tyler  staff    0 Nov 29 14:23 baz
drwxr-xr-x  2 tyler  staff   68 Nov 29 14:24 boos/

./bar/boos:
total 0

./foo:
total 0
-rw-r--r--  1 tyler  staff    0 Nov 29 14:23 baa.a
-rw-r--r--  1 tyler  staff    0 Nov 29 14:23 baz.a

Soyokaze:test$ tar -cf - */ | tar -cf myfiles.tar --include="*.a" @-

Soyokaze:test$ tar -tf myfiles.tar
bar/baa.a
bar/baaa.a
foo/baa.a
foo/baz.a

You must log in to answer this question.

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