installed_progs.t: Python checks stdout too, 150 ok
[sunny256-utils.git] / mktar
blobad38dffc764b33b56c71fd848b12201d613fd6bb
1 #!/usr/bin/env bash
3 #=======================================================================
4 # mktar
5 # File ID: 5818b856-0ba9-11de-b2c1-000475e441b9
7 # Move a whole subdirectory tree into a single .tar.gz file.
9 # Author: Øyvind A. Holm <sunny@sunbase.org>
10 # License: GNU General Public License version 2 or later.
11 #=======================================================================
13 progname=mktar
14 VERSION=0.29.1
16 STD_SUFFIX=''
18 ARGS="$(getopt -o "\
29 o:\
30 P:\
33 S:\
34 s:\
39 " -l "\
40 dirs-only,\
41 force,\
42 help,\
43 incremental,\
44 dereference,\
45 random-mac,\
46 no-git-check,\
47 no-actime,\
48 no-uuid,\
49 no-xattrs,\
50 numeric-owner,\
51 output-dir:,\
52 prefix:,\
53 quiet,\
54 remove-files,\
55 suffix:,\
56 split:,\
57 stdout,\
58 verbose,\
59 version,\
60 xz,\
61 gzip,\
62 " -n "$progname" -- "$@")"
63 test "$?" = "0" || exit 1
64 eval set -- "$ARGS"
66 opt_1=0
67 opt_no_git_check=0
68 opt_dirs_only=0
69 opt_force=0
70 opt_help=0
71 opt_incremental=0
72 opt_dereference=0
73 opt_random_mac=0
74 opt_no_uuid=0
75 opt_no_actime=0
76 opt_no_xattrs=0
77 opt_numeric_owner=0
78 opt_output_dir=''
79 opt_prefix=''
80 opt_quiet=0
81 opt_remove_files=0
82 opt_suffix="$STD_SUFFIX"
83 opt_split=''
84 opt_stdout=0
85 opt_verbose=0
86 opt_xz=0
87 opt_gzip=0
88 while :; do
89 case "$1" in
90 -1) opt_1=1; shift ;;
91 -G|--no-git-check) opt_no_git_check=1; shift ;;
92 -d|--dirs-only) opt_dirs_only=1; shift ;;
93 -f|--force) opt_force=1; shift ;;
94 -h|--help) opt_help=1; shift ;;
95 -i|--incremental) opt_incremental=1; shift ;;
96 -L|--dereference) opt_dereference=1; shift ;;
97 -m|--random-mac) opt_random_mac=1; shift ;;
98 --no-uuid) opt_no_uuid=1; shift ;;
99 --numeric-owner) opt_numeric_owner=1; shift ;;
100 -o|--output-dir) opt_output_dir="$2"; shift 2 ;;
101 -P|--prefix) opt_prefix="$2"; shift 2 ;;
102 -q|--quiet) opt_quiet=$(($opt_quiet + 1)); shift ;;
103 -r|--remove-files) opt_remove_files=1; shift ;;
104 -S|--suffix) opt_suffix="$2"; shift 2 ;;
105 -s|--split) opt_split="$2"; shift 2 ;;
106 -O|--stdout) opt_stdout=1; shift ;;
107 -v|--verbose) opt_verbose=$(($opt_verbose + 1)); shift ;;
108 --version) echo $progname $VERSION; exit 0 ;;
109 -A|--no-actime) opt_no_actime=1; shift ;;
110 -X|--no-xattrs) opt_no_xattrs=1; shift ;;
111 -x|--xz) opt_xz=1; shift ;;
112 -z|--gzip) opt_gzip=1; shift ;;
113 --) shift; break ;;
114 *) echo $progname: Internal error >&2; exit 1 ;;
115 esac
116 done
117 opt_verbose=$(($opt_verbose - $opt_quiet))
119 if test "$opt_help" = "1"; then
120 test $opt_verbose -gt 0 && { echo; echo $progname $VERSION; }
121 cat <<END
123 Move a whole subdirectory tree into a single $STD_SUFFIX.tar.gz file.
125 Usage: $progname [options] DIRECTORY [DIRECTORIES [...]]
127 Options:
130 Alias for "--split 1G". No file systems should have problems with
131 this size, and it makes it easy to calculate the size of the .tar
132 file.
133 -d, --dirs-only
134 Ignore non-directory arguments.
135 -f, --force
136 Don't abort if the .tar file exists, delete it before proceeding.
137 -G, --no-git-check
138 To protect against potential data loss, it first loops through all
139 arguments to check that none of them are stored in Git. If any files
140 are stored under any of the directories, it aborts. Useful with for
141 example git-annex, where only a symlink would be stored in the
142 archive file. This option disables the check.
143 -h, --help
144 Show this help.
145 -i, --incremental
146 Create a .snar file with information for creating incremental
147 backups. Uses the -g/--listed-incremental option in GNU tar.
148 -L, --dereference
149 Don't pack symlinks, include the actual files they point to.
150 -m, --random-mac
151 Use random MAC address in UUID file label.
152 --no-uuid
153 Don't create UUID label in the .tar file.
154 --numeric-owner
155 Use numbers for user/group names.
156 -O, --stdout
157 Send the .tar output to stdout instead of creating files on disk.
158 All nonsensical options are ignored, for example --split. Does not
159 work with compression.
160 -o DIR, --output-dir DIR
161 Store the tar files in DIR instead of the current directory.
162 -P PREFIX, --prefix PREFIX
163 Use PREFIX at the beginning of the tar filenames. Doesn't change the
164 name of the extracted files. Adds a terminating '.' if it doesn't
165 exist.
166 -q, --quiet
167 Be more quiet. Can be repeated to increase silence.
168 -r, --remove-files
169 Remove files in DIRECTORY immediately after they've been added to
170 the archive. Can be used when there's not enough disk space for the
171 archive.
172 -S SUFFIX, --suffix SUFFIX
173 Add a custom suffix after the directory name and before the .tar
174 extension in the output filename. No changes are made to the
175 resulting tar file, only the file name is different. An intial '.'
176 is added if it's missing.
177 Default value: "$STD_SUFFIX".
178 -s SIZE, --split SIZE
179 Split the .tar file into files with SIZE bytes each. Allowed values
180 are those understood by the -b/--bytes option in split(1). These
181 files are not compressed by default, to make it easier to extract
182 data from the files without starting from the beginning.
183 -v, --verbose
184 Increase level of verbosity. Can be repeated.
185 --version
186 Print version information.
187 -A, --no-actime
188 Don't store atime or ctime in the tar file. Use with --no-uuid to
189 generate identical tar files.
190 -X, --no-xattrs
191 Don't use --xattrs with tar(1), create standard tar archive without
192 extended attributes and nanoseconds.
193 -x, --xz
194 Compress the archives with xz(1) after the files are added.
195 -z, --gzip
196 Compress the archives with gzip(1) after the files are added.
199 exit 0
202 if test "$opt_xz" = "1" -a "$opt_gzip" = "1"; then
203 echo $progname: Cannot mix the --gzip and --xz options >&2
204 exit 1
207 if test "$opt_1" = "1" -a -n "$opt_split"; then
208 echo $progname: Cannot mix the -1 and --split options >&2
209 exit 1
212 if test "$opt_stdout" = "1" -a -n "$(echo $opt_gzip$opt_xz | grep 1)"; then
213 echo $progname: Cannot use compression with -O/--stdout >&2
214 exit 1
217 test "$opt_1" = "1" && opt_split=1G
219 if test "$opt_dereference" = "1"; then
220 dereference_str="--dereference"
221 else
222 dereference_str=""
225 if test "$opt_no_actime" = "1"; then
226 actime_str="--pax-option=delete=atime,delete=ctime"
227 else
228 actime_str=""
231 if test "$opt_random_mac" = "1"; then
232 random_mac_str="--random-mac"
233 else
234 random_mac_str=""
237 if test "$opt_remove_files" = "1"; then
238 rm_files_str="--remove-files"
239 else
240 rm_files_str=""
243 if test "$opt_no_xattrs" = "1"; then
244 xattrs_str=""
245 else
246 xattrs_str="--xattrs"
249 if test "$opt_numeric_owner" = "1"; then
250 numeric_str="--numeric-owner"
251 else
252 numeric_str=""
255 for f in "$@"; do
256 if test -d "$f" -o "$opt_dirs_only" != "1"; then
257 if test "$opt_no_git_check" != "1"; then
258 git ls-files "$f" | grep -q . >&2 && {
259 echo -n "$progname: $f: " >&2
260 if test -d "$f"; then
261 echo Files are stored in Git below this directory >&2
262 else
263 echo File is stored in Git >&2
265 echo $progname: Use -G/--no-git-check to add it anyway >&2
266 exit 1
269 else
270 echo $progname: $f: Not a directory >&2
272 done
274 prefix="$opt_prefix"
275 if test -n "$prefix"; then
276 echo "$prefix" | grep -q '\.$' || prefix="$prefix."
279 suffix="$opt_suffix"
280 if test -n "$suffix"; then
281 echo "$suffix" | grep -q '^\.' || suffix=".$suffix"
284 if test -n "$opt_output_dir"; then
285 if test ! -d "$opt_output_dir/."; then
286 echo "$progname: $opt_output_dir: Directory not found" >&2
287 exit 1
289 output_dir="$opt_output_dir/"
290 else
291 output_dir="";
294 for f in "$@"; do
295 dir="$(echo -n "$f" | sed 's/\/*$//')"
296 if [ -z "$dir" ]; then
297 echo "$progname: $f: Argument is empty after stripping slash" >&2
298 exit 1
300 tarfile="$output_dir$prefix$dir$suffix.tar"
302 if test $opt_verbose -ge 2; then
303 echo $progname: dir = $dir >&2
306 if [ -d "$dir" -o "$opt_dirs_only" != "1" ]; then
307 echo >&2
308 echo $progname: Packing $dir... >&2
310 if test "$opt_stdout" != "1"; then
311 if ls "$tarfile"* 2>/dev/null | grep -q .; then
312 if test "$opt_force" = "1"; then
313 rm -fv "$tarfile"*
314 else
315 echo "$progname: $tarfile* already exist" >&2
316 exit 1
321 echo "$dir" | grep -q / && {
322 echo "$progname: $dir: Slashes not allowed in the file name" >&2
323 exit 1
326 if test "$opt_no_uuid" = "1"; then
327 label_str=""
328 else
329 uuid=$(
330 suuid -t mktar --raw -w eo $random_mac_str \
331 -c "<c_mktar> <filename>$tarfile</filename> <host>$(
332 hostname
333 )</host> <directory>$(/bin/pwd)</directory> </c_mktar>"
334 ) || {
335 echo $progname: suuid error >&2
336 exit 1
338 label_str="--label=$uuid"
340 if test "$opt_incremental" = "1"; then
341 incremental_str="--listed-incremental $prefix$dir$suffix.snar"
342 else
343 incremental_str=""
345 tar_args="$incremental_str $rm_files_str"
346 tar_args="$tar_args --force-local $actime_str --sort=name --sparse"
347 tar_args="$tar_args $dereference_str $numeric_str $xattrs_str"
348 tar_args="$tar_args $label_str"
349 if test "$opt_stdout" = "1"; then
350 echo $progname: Sending to stdout >&2
351 echo $progname: tar c $tar_args $dir >&2
352 tar c $tar_args "$dir" || exit 1
353 continue
355 if test -n "$opt_split"; then
356 echo $progname: tar c $tar_args $dir \
357 \| split -b $opt_split --verbose - $tarfile.split_ >&2
358 tar c $tar_args "$dir" \
359 | split -b $opt_split --verbose - "$tarfile.split_" || exit 1
360 if test ! -e "$tarfile.split_ab"; then
361 mv -vi "$tarfile.split_aa" "$tarfile" || exit 1
363 test "$opt_xz" = "1" && xz -v "$tarfile"*
364 test "$opt_gzip" = "1" && gzip -vn --rsyncable "$tarfile"*
365 else
366 echo $progname: tar cf $tarfile $tar_args $dir >&2
367 tar cf "$tarfile" $tar_args "$dir" || exit 1
368 if test "$opt_stdout" != "1"; then
369 du --si "$tarfile" >&2
370 test "$opt_xz" = "1" && xz -v "$tarfile"
371 test "$opt_gzip" = "1" && gzip -vn --rsyncable "$tarfile"
375 done
377 exit 0