How do I tar a directory of files and folders without including the directory itself?
Question
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
Solution
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
OTHER TIPS
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.
You can also create archive as usual and extract it with:
tar --strip-components 1 -xvf my_directory.tar.gz
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
TL;DR
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 -
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
cd my_directory
tar zcvf ../my_directory.tar.gz *
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.
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.
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 :
Use pax.
Pax is a deprecated package but does the job perfectly and in a simple fashion.
pax -w > mydir.tar mydir
Simplest way I found:
cd my_dir && tar -czvf ../my_dir.tar.gz *
# 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.
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
tar -C my_dir -zcvf my_dir.tar.gz `ls my_dir`