513

I typically do:

tar -czvf my_directory.tar.gz my_directory

What if I just want to include everything (including any hidden system files) in my_directory, but not the directory itself? I don't want:

my_directory
   --- my_file
   --- my_file
   --- my_file

I want:

my_file
my_file
my_file
l'L'l
  • 44,951
  • 10
  • 95
  • 146
  • Is that the default behavior of doing `tar -czf`? In my case it's only storing the files and not the directory. When I just `tar` the directory it includes it but with `tar -czf` it is only adding the files. – Durga Swaroop Jan 05 '17 at 12:29

22 Answers22

809

Use the -C switch of tar:

tar -czvf my_directory.tar.gz -C my_directory .

The -C my_directory tells tar to change the current directory to my_directory, and then . means "add the entire current directory" (including hidden files and sub-directories).

Make sure you do -C my_directory before you do . or else you'll get the files in the current directory.

Warning: you'll get entries as ./file-name.ext instead of file-name.ext!

If you need entries in the form of file-name.ext, read other answers.

izogfif
  • 6,000
  • 2
  • 35
  • 25
dubek
  • 11,447
  • 5
  • 30
  • 23
  • 6
    +1 thank you! It was the damn '.' I was missing. so aggravating – JCotton May 05 '11 at 02:08
  • 17
    "Unlike most options, -C is processed at the point it occurs within the list of files to be processed. Consider the following command: `tar --create --file=foo.tar -C /etc passwd hosts -C /lib libc.a`" http://www.apl.jhu.edu/Misc/Unix-info/tar/tar_65.html I always try `tar -czvf my_directory.tar.gz * -C my_directory` and that does not work. `-C` location is important! Damn tar... – m-ric Jan 04 '13 at 16:47
  • 79
    Not perfect - tar file contains '.' and also ./file1 instead of just file1. I like the solution by mateusza below to use --strip-components when un-tarring. – Ivan Apr 04 '13 at 14:46
  • 2
    weirdly `-C` fails if combined with globbing wildcards. It globs in the _real_ current directory, and then tries to find the files in the _tar_ current directory. `tar cf foo.tar -C /lib lib*` results in `tar: lib*: Cannot stat: No such file or directory` – Superole May 27 '13 at 12:55
  • 3
    @Superole: the _shell_ substitues the wildcards before running tar. Also note that using a wildcard like `*` will not include hidden files (which was the original requirement). – dubek Jun 09 '13 at 07:09
  • I didn't know about the "." to add the _entire_ directory. Liking the two-in-one recipe by dubek! – tuk0z Dec 13 '14 at 13:06
  • This is especially useful when you're using `tar` as a replacement for `cp`. – David Ehrmann Jan 20 '15 at 23:07
  • 46
    It creates . as a root directory in .tar.gz. – Anonymous Mar 03 '15 at 02:46
  • 4
    For those who don't want the `.` parent directory, [here's the solution](http://stackoverflow.com/a/39530409/1000608) – aross Mar 08 '17 at 11:31
  • 1
    I realized that the "." HAS to come after the `-C` else tar just tries to archive the current directory without doing the change dir first – S Raghav Apr 17 '22 at 18:37
266
cd my_directory/ && tar -zcvf ../my_dir.tgz . && cd - 

should do the job in one line. It works well for hidden files as well. "*" doesn't expand hidden files by path name expansion at least in bash. Below is my experiment:

$ mkdir my_directory
$ touch my_directory/file1
$ touch my_directory/file2
$ touch my_directory/.hiddenfile1
$ touch my_directory/.hiddenfile2
$ cd my_directory/ && tar -zcvf ../my_dir.tgz . && cd ..
./
./file1
./file2
./.hiddenfile1
./.hiddenfile2
$ tar ztf my_dir.tgz
./
./file1
./file2
./.hiddenfile1
./.hiddenfile2
Asclepius
  • 57,944
  • 17
  • 167
  • 143
tomoe
  • 2,966
  • 1
  • 16
  • 4
  • 2
    This will also work on files with spaces or other special characters. Good job! – PanCrit Jun 03 '09 at 20:45
  • 44
    Not perfect - tar file contains '.' and also ./file1 instead of just file1. I like the solution by mateusza below to use --strip-components when un-tarring. – Ivan Apr 04 '13 at 14:45
  • 30
    @Ivan if you replace `.` with `*` so the command will be `cd my_directory/ && tar -zcvf ../my_dir.tgz * && cd ..` then it will work as you expected. – Anonymous Mar 03 '15 at 02:47
  • 4
    @jmathew You can also use a subshell so your current shell's working directory doesn't change: `$ (cd my_directory/ && tar -zcvf ../my_dir.tgz .)` – alecbz Aug 31 '17 at 00:43
  • I know it's an old answer but `cd`ing into directories and out is pretty lame. Could at least use `pushd` and `popd` if `tar` didn't have any flags like `-C`. – Andris Feb 19 '19 at 13:04
  • 8
    Does anyone know why this is so complicated? Seems like a big oversight of the `tar` creators... – information_interchange Mar 22 '21 at 16:29
  • 5
    on current ubuntu this gives me an archive with a folder named “.” in it. –  Apr 25 '21 at 19:09
  • Wondering why tar command never included this common feature. – Cartucho Dec 30 '21 at 23:21
79

You can also create archive as usual and extract it with:

tar --strip-components 1 -xvf my_directory.tar.gz
mateusza
  • 5,341
  • 2
  • 24
  • 20
  • 5
    This solution is especially good in situations where you are working with tarballs created before all of your requirements were known... – Digger Sep 21 '16 at 22:24
  • 10
    Mind that `--strip-components` is a GNU extension. – zneak Aug 13 '18 at 22:33
  • 5
    This answer can be improved by providing the "as usual" example in context. – vhs Mar 23 '19 at 06:52
  • 3
    If the creation of tar is on my side but the extraction is not and expects no `.` as root dir, this will not solve the problem. – WesternGun Dec 22 '21 at 14:29
58

TL;DR (no ./ and no ./file1!)

find /my/dir/ -printf "%P\n" | tar -czf mydir.tgz --no-recursion -C /my/dir/ -T -

With some conditions (archive only files, dirs and symlinks):

find /my/dir/ -printf "%P\n" -type f -o -type l -o -type d | tar -czf mydir.tgz --no-recursion -C /my/dir/ -T -

Explanation

The below unfortunately includes a parent directory ./ in the archive:

tar -czf mydir.tgz -C /my/dir .

You can move all the files out of that directory by using the --transform configuration option, but that doesn't get rid of the . directory itself. It becomes increasingly difficult to tame the command.

You could use $(find ...) to add a file list to the command (like in magnus' answer), but that potentially causes a "file list too long" error. The best way is to combine it with tar's -T option, like this:

find /my/dir/ -printf "%P\n" -type f -o -type l -o -type d | tar -czf mydir.tgz --no-recursion -C /my/dir/ -T -

Basically what it does is list all files (-type f), links (-type l) and subdirectories (-type d) under your directory, make all filenames relative using -printf "%P\n", and then pass that to the tar command (it takes filenames from STDIN using -T -). The -C option is needed so tar knows where the files with relative names are located. The --no-recursion flag is so that tar doesn't recurse into folders it is told to archive (causing duplicate files).

If you need to do something special with filenames (filtering, following symlinks etc), the find command is pretty powerful, and you can test it by just removing the tar part of the above command:

$ find /my/dir/ -printf "%P\n" -type f -o -type l -o -type d
> textfile.txt
> documentation.pdf
> subfolder2
> subfolder
> subfolder/.gitignore

For example if you want to filter PDF files, add ! -name '*.pdf'

$ find /my/dir/ -printf "%P\n" -type f ! -name '*.pdf' -o -type l -o -type d
> textfile.txt
> subfolder2
> subfolder
> subfolder/.gitignore

Non-GNU find

The command uses printf (available in GNU find) which tells find to print its results with relative paths. However, if you don't have GNU find, this works to make the paths relative (removes parents with sed):

find /my/dir/ -type f -o -type l -o -type d | sed s,^/my/dir/,, | tar -czf mydir.tgz --no-recursion -C /my/dir/ -T -
aross
  • 3,325
  • 3
  • 34
  • 42
  • 4
    Great answer. Very elaborate, and most importantly, solves the problem perfectly. – Alex Mar 31 '17 at 15:40
  • 1
    Nice workaround. Why is tar so stup1d? – SandRock Sep 29 '21 at 18:34
  • @SandRock I agree it's strange that achieving something so basic as this is so tricky with tar. Probably just historical reasons. – aross Sep 30 '21 at 08:28
  • Like this, I will define a function in `.bashrc` for it, named `tar_content` – WesternGun Dec 22 '21 at 14:42
  • 1
    Why isn’t this the most uprated and accepted solution. I’m so grateful: `-T -` did the trick for me. – matt Mar 15 '23 at 21:23
  • @matt Thanks for your comment. Unfortunately getting past older answers is hard because they've had more time to accumulate upvotes. It can take years. In that regard, the new "trending" sort option does help. – aross Mar 16 '23 at 08:49
  • 1
    No impact here, but thx for the hint.. I'll use it from now on :) – matt Apr 03 '23 at 12:48
37

Have a look at --transform/--xform, it gives you the opportunity to massage the file name as the file is added to the archive:

% mkdir my_directory
% touch my_directory/file1
% touch my_directory/file2
% touch my_directory/.hiddenfile1
% touch my_directory/.hiddenfile2
% tar -v -c -f my_dir.tgz --xform='s,my_directory/,,' $(find my_directory -type f)
my_directory/file2
my_directory/.hiddenfile1
my_directory/.hiddenfile2
my_directory/file1
% tar -t -f my_dir.tgz 
file2
.hiddenfile1
.hiddenfile2
file1

Transform expression is similar to that of sed, and we can use separators other than / (, in the above example).
https://www.gnu.org/software/tar/manual/html_section/tar_52.html

leesei
  • 6,020
  • 2
  • 29
  • 51
Magnus
  • 4,644
  • 1
  • 33
  • 49
  • 4
    I would do this. Anything else is just a hack! – jwg Aug 10 '15 at 07:31
  • 1
    Good solution, but it might cause `file list too long`. [My solution](http://stackoverflow.com/a/39530409/1000608) prevents that and is more flexible as well. – aross Mar 08 '17 at 12:56
  • 1
    This is a great solution. You can also pass `--xform` multiple times for multiple paths. – elig Jan 02 '20 at 15:51
30
cd my_directory
tar zcvf ../my_directory.tar.gz *
mateusza
  • 5,341
  • 2
  • 24
  • 20
20

This Answer should work in most situations. Notice however how the filenames are stored in the tar file as, for example, ./file1 rather than just file1. I found that this caused problems when using this method to manipulate tarballs used as package files in BuildRoot.

One solution is to use some Bash globs to list all files except for .. like this:

tar -C my_dir -zcvf my_dir.tar.gz .[^.]* ..?* *

This is a trick I learnt from this answer.

Now tar will return an error if there are no files matching ..?* or .[^.]* , but it will still work. If the error is a problem (you are checking for success in a script), this works:

shopt -s nullglob
tar -C my_dir -zcvf my_dir.tar.gz .[^.]* ..?* *
shopt -u nullglob

Though now we are messing with shell options, we might decide that it is neater to have * match hidden files:

shopt -s dotglob
tar -C my_dir -zcvf my_dir.tar.gz *
shopt -u dotglob

This might not work where your shell globs * in the current directory, so alternatively, use:

shopt -s dotglob
cd my_dir
tar -zcvf ../my_dir.tar.gz *
cd ..
shopt -u dotglob
Community
  • 1
  • 1
Rob Fisher
  • 945
  • 1
  • 9
  • 20
  • 2
    I get wierd errors when I do this `tar: start.sh: Cannot stat: No such file or directory` This happens to all files in my current directory! How do I avoid this? – BrainStone Oct 03 '13 at 23:49
  • @BrainStone I get exactly the same results. – mbmast Dec 21 '16 at 20:37
  • This does not work - at least in some shells (e.g., bash, version 5.0.17, Ubuntu 20.04) - because the `*` glob is evaluated by the shell before tar takes over and changes directory (`-C my_dir`). So, it tries to archive the files in the current dir where the tar command is executed, instead of the changed-dir, `my_dir`. You may get lucky, if the file names in the current dir just happen to match the names in the changed-dir, `my_dir`, but that's not generally reliable. :) ... Most likely, this is the reason for the errors above. – Trevor Nov 15 '21 at 20:40
  • 1
    @Trevor I think this is what the 4th example works around (cd to the directory first, then run tar without the -C option) – Rob Fisher Dec 02 '21 at 16:55
6
cd my_directory && tar -czvf ../my_directory.tar.gz $(ls -A) && cd ..

This one worked for me and it's include all hidden files without putting all files in a root directory named "." like in tomoe's answer :

Community
  • 1
  • 1
med
  • 61
  • 1
  • 2
  • 1
    `tar` will break if supplied by `ls` file or dir name is having a space. [More reasons why not to use ls](http://mywiki.wooledge.org/BashPitfalls#for_f_in_.24.28ls_.2A.mp3.29) – Jimmix Mar 02 '21 at 16:08
4

Command

to create a standard archive file.

find my_directory/ -maxdepth 1 -printf "%P\n" | tar -cvf my_archive.tar -C my_directory/ -T -

Packed files and dirs are in the root of the archive without path info and deeper files have relative path.
There are no weird looking './' in front of files and dirs. ('./file')
No special files '.' are included.

It seems that another tool, like find or ls (ls -A -1) is needed to accomplish these goals and tar using just its arguments is unable to pick files and create an archive with such requirements.

Using above command creates an archive tar file which can be further processed or delivered to someone without looking weird or needing an explanation or a tool to unpack.

Arguments description

-maxdepth 1
Descend at most 1 level - No recursing.
-printf
print format on the standard output
%P File's name with the name of the starting-point under which it was found removed.
\n Newline
printf does not add a newline at the end of the string. It must be added here

tar:
-C DIR, --directory=DIR
change to directory DIR

-T FILE, --files-from=FILE
get names to extract or create from FILE
-
that FILE from above is the standard input, from the pipe


Comments on other solutions.

The same result might be achieved using solution described by @aross.
The difference with the solution here is in that which tool is doing the recursing. If you leave the job to find, every filepath name, goes through the pipe. It also sends all directory names, which tar with --no-recursion ignores or adds as empty ones followed by all files in each directory. If there was unexpected output as errors in file read from find, tar would not know or care what's going on.
But with further checks, like processing error stream from find, it might be a good solution where many options and filters on files are required.
I prefer to leave the recursing on tar, it does seem simpler and as such more stable solution.
With my complicated directory structure, I feel more confident the archive is complete when tar will not report an error.

Another solution using find proposed by @serendrewpity seems to be fine, but it fails on filenames with spaces. Difference is that output from find supplied by $() sub-shell is space-divided. It might be possible to add quotes using printf, but it would further complicate the statement.

There is no reason to cd into the my_directory and then back, while using ../my_archive.tar for tar path, because TAR has -C DIR, --directory=DIR command which is there just for this purpose.

Using . (dot) will include dots

Using * will let shell supply the input file list. It might be possible using shell options to include dot files. But it's complicated. The command must be executed in shell which allows that. Enabling and disabling must be done before and after tar command. And it will fail if root dir of future archive contains too many files.

That last point also applies to all those solutions which are not using pipe.

Most of solutions are creating a dir inside which are the files and dirs. That is barely ever desired.

papo
  • 1,606
  • 19
  • 18
  • You could add `2>/dev/null` to find. Then you're guaranteed to only have filenames/paths – aross Dec 22 '21 at 14:51
  • Please explain. I don't see how would that work. Do you mean to filter out errors, like inaccessible files, without permission? 1. I would like to see errors. If I was expecting to not include all files, I would rather exclude such using a command. 2. pipe only works on stdout. Anything printed to stderr will not reach `tar` on the right side of pipe `|` but will by default be printed to console. – papo Dec 23 '21 at 02:31
  • Filter out errors was the idea yes, I wasn't sure whether stderr would also be piped though... But then there's no issue, is there? – aross Dec 23 '21 at 09:59
  • Unless you mean there's a read error on the file... which means you have a file with no permissions. Very unusual, but possible. In that case you can add `--ignore-failed-read` to tar – aross Dec 23 '21 at 10:05
4
cd DIRECTORY
tar -czf NAME.tar.gz  *

the asterisk will include everything even hidden ones

3

If it's a Unix/Linux system, and you care about hidden files (which will be missed by *), you need to do:

cd my_directory
tar zcvf ../my_directory.tar.gz * .??*

I don't know what hidden files look like under Windows.

tshepang
  • 12,111
  • 21
  • 91
  • 136
PanCrit
  • 2,688
  • 1
  • 18
  • 16
2

I would propose the following Bash function (first argument is the path to the dir, second argument is the basename of resulting archive):

function tar_dir_contents ()
{
    local DIRPATH="$1"
    local TARARCH="$2.tar.gz"
    local ORGIFS="$IFS"
    IFS=$'\n'
    tar -C "$DIRPATH" -czf "$TARARCH" $( ls -a "$DIRPATH" | grep -v '\(^\.$\)\|\(^\.\.$\)' )
    IFS="$ORGIFS"
}

You can run it in this way:

$ tar_dir_contents /path/to/some/dir my_archive

and it will generate the archive my_archive.tar.gz within current directory. It works with hidden (.*) elements and with elements with spaces in their filename.

gpz500
  • 21
  • 2
  • Avoid using `ls` for that [link](http://mywiki.wooledge.org/BashPitfalls#for_f_in_.24.28ls_.2A.mp3.29) – Jimmix Mar 02 '21 at 16:13
1

This is what works for me.

tar -cvf my_dir.tar.gz -C /my_dir/ $(find /my_dir/ -maxdepth 1 -printf '%P ')

You could also use

tar -cvf my_dir.tar.gz -C /my_dir/ $(find /my_dir/ -mindepth 1 -maxdepth 1 -printf '%P ')

In the first command, find returns a list of files and sub-directories of my_dir. However, the directory my_dir is itself included in that list as '.' The -printf parameter removes the full path including that '.' and also all However the in the format string '%P ' of printf leaves a remnant in the list of files and sub-directories of my_dir and can be seen by a leading space in the result of the find command.

That will not be a problem for TAR but if you want to fix this, add -mindepth 1 as in the second command.

1
tar -czvf mydir.tgz -C my_dir/ `ls -A mydir`

Run it one level above mydir. This won't include any [.] or stuff.

Airstriker
  • 331
  • 2
  • 5
  • 1
    This will not include any file/dir with a space either. [link](http://mywiki.wooledge.org/BashPitfalls#for_f_in_.24.28ls_.2A.mp3.29) – Jimmix Mar 02 '21 at 16:14
1

If you use bash, this could be the simplest answer. With shopt -s dotglob the bash command would:

  1. not to include the root directory.
  2. will include hiddlen files in the root directory.
cd my_directory
shopt -s dotglob
tar -czvf ../my_directory.tar.gz *
shopt -u dotglob

(from https://stackoverflow.com/a/9776491/7726468)

Test this solution: enter image description here

ws_
  • 1,076
  • 9
  • 18
0

Use pax.

Pax is a deprecated package but does the job perfectly and in a simple fashion.

pax -w > mydir.tar mydir
rjv
  • 6,058
  • 5
  • 27
  • 49
0
# tar all files within and deeper in a given directory
# with no prefixes ( neither <directory>/ nor ./ )
# parameters: <source directory> <target archive file>
function tar_all_in_dir {
    { cd "$1" && find -type f -print0; } \
    | cut --zero-terminated --characters=3- \
    | tar --create --file="$2" --directory="$1" --null --files-from=-
}

Safely handles filenames with spaces or other unusual characters. You can optionally add a -name '*.sql' or similar filter to the find command to limit the files included.

marcingo
  • 61
  • 2
  • 8
0

Simplest way I found:

cd my_dir && tar -czvf ../my_dir.tar.gz *

alexgo
  • 41
  • 5
0
function tar.create() {
        local folder="${1}"
        
        local tar="$(basename "${folder}")".tar.gz
        
        cd "${folder}" && tar -zcvf "../${tar}" .; cd - &> /dev/null
}

Example:

tar.create /path/to/folder

You are welcome.

mjs
  • 21,431
  • 31
  • 118
  • 200
-2
 tar -cvzf  tarlearn.tar.gz --remove-files mytemp/*

If the folder is mytemp then if you apply the above it will zip and remove all the files in the folder but leave it alone

 tar -cvzf  tarlearn.tar.gz --remove-files --exclude='*12_2008*' --no-recursion mytemp/*

You can give exclude patterns and also specify not to look into subfolders too

Andrew Barber
  • 39,603
  • 20
  • 94
  • 123
user1456599
  • 481
  • 5
  • 11
-2

tar archive:

# tar -czvf mydir.tar.gz mydir

untar archive:

# tar -xf mydir.tar.gz
burtsevyg
  • 3,851
  • 2
  • 29
  • 44
-5
tar -C my_dir -zcvf my_dir.tar.gz `ls my_dir`
mateusza
  • 5,341
  • 2
  • 24
  • 20