tar 命令详解与参数实测(Ubuntu 22.04)

tar 全参数实测:-c/-t/-x 主模式与漏写 -f 的三种结局、-z/-j/-J/-a 压缩与体积对比、--exclude/--exclude-vcs 排除、-r/-u/--delete 归档维护与压缩档限制、-d 差异比对、--strip-components/-h/-O/--totals、-p/--numeric-owner 权限属主、-xfk 粘连陷阱与截断归档排障

Linux命令Linux系统UbuntuUbuntu 22.04命令行压缩归档2026/10/16

tar 命令详解与参数实测(Ubuntu 22.04)

目标:全面掌握 GNU tar 的归档与解包能力——-c/-t/-x 三种主模式与 -f 的必备性(漏写后的三种结局)、-v 长列表与 --checkpoint 进度、绝对路径剥离与 -C 目录切换、--wildcards/--strip-components 精细解包、-z/-j/-J/-a 压缩过滤与体积对比、--exclude/--exclude-vcs 排除、-r/-u/--delete 归档维护与压缩档限制、-d 差异比对、-p/--same-owner/--numeric-owner 权限属主、-O 标准输出、-h 软链接、--totals 统计、覆盖控制 -xfk 粘连陷阱以及截断归档排障,全部命令与回显已在 Ubuntu 22.04(GNU tar 1.34)实测通过。

一、环境信息

本节列出本文所有实测所依赖的环境与测试数据,便于读者按同规格复现。

  • 操作系统:Ubuntu 22.04.5 LTS(x86_64)
  • tar 版本:GNU tar 1.34(2.3 节实测)
  • 压缩配套:gzip、bzip2、xz 均为系统预装命令,-z/-j/-J 直接调用,无需额外安装
  • 演示目录:/root/demo/tar/
  • 测试数据:project/ 目录 6 个文件(Makefile 113 B、config.ini 96 B、docs/readme.md 164 B、src/main.c 435 B、src/util.py 372 B、src/access.log 2,628,894 字节日志类高重复文本),内容为短小的源码与配置,复现时任意同量级文本均可
  • 进度说明:3.9 节会向 project/config.ini 追加一行(96 B → 123 B),此后章节涉及该文件的字节数均以 123 B 为准;3.13 节会临时创建软链接 cfg-link

二、命令概览

2.1 语法与测试数据准备

tar 的基本语法是 tar [OPTION...] [FILE]...,与 zip 这类"一条命令包打天下"的工具不同:tar 必须给出一个主操作模式(-c 创建、-x 解包、-t 列出、-r 追加、-u 更新、-d 比对、-A 合并、--delete 删除),-f 指定归档文件(缺省指向标准输入/输出)。FILE 既是要打包的文件/目录(创建模式),也是要从归档中挑选的条目名(读取模式)。

先核对演示数据。project/ 模拟一个典型源码工程(六个文件内容为任意短小的源码与配置,字节数见环境信息),其中 src/access.log 是 2.6M 的日志类文本,用于让归档体积差异可感知:

$ find project -type f | sort
project/Makefile
project/config.ini
project/docs/readme.md
project/src/access.log
project/src/main.c
project/src/util.py

$ ls -lh project/src/access.log
-rw-r--r-- 1 root root 2.6M Oct 15 18:46 project/src/access.log

2.2 --help 帮助全量回显

tar --help 输出全量帮助(实测原样保留,含参数分组、归档格式说明与默认值,可作为参数速查表):

Usage: tar [OPTION...] [FILE]...
GNU 'tar' saves many files together into a single tape or disk archive, and can
restore individual files from the archive.

Examples:
  tar -cf archive.tar foo bar  # Create archive.tar from files foo and bar.
  tar -tvf archive.tar         # List all files in archive.tar verbosely.
  tar -xf archive.tar          # Extract all files from archive.tar.

 Main operation mode:
  -A, --catenate, --concatenate   append tar files to an archive
  -c, --create               create a new archive
      --delete               delete from the archive (not on mag tapes!)
  -d, --diff, --compare      find differences between archive and file system
  -r, --append               append files to the end of an archive
      --test-label           test the archive volume label and exit
  -t, --list                 list the contents of an archive
  -u, --update               only append files newer than copy in archive
  -x, --extract, --get       extract files from an archive

 Operation modifiers:

      --check-device         check device numbers when creating incremental
                             archives (default)
  -g, --listed-incremental=FILE   handle new GNU-format incremental backup
  -G, --incremental          handle old GNU-format incremental backup
      --hole-detection=TYPE  technique to detect holes
      --ignore-failed-read   do not exit with nonzero on unreadable files
      --level=NUMBER         dump level for created listed-incremental archive
      --no-check-device      do not check device numbers when creating
                             incremental archives
      --no-seek              archive is not seekable
  -n, --seek                 archive is seekable
      --occurrence[=NUMBER]  process only the NUMBERth occurrence of each file
                             in the archive; this option is valid only in
                             conjunction with one of the subcommands --delete,
                             --diff, --extract or --list and when a list of
                             files is given either on the command line or via
                             the -T option; NUMBER defaults to 1
      --sparse-version=MAJOR[.MINOR]
                             set version of the sparse format to use (implies
                             --sparse)
  -S, --sparse               handle sparse files efficiently

 Local file name selection:
      --add-file=FILE        add given FILE to the archive (useful if its name
                             starts with a dash)
  -C, --directory=DIR        change to directory DIR
      --exclude=PATTERN      exclude files, given as a PATTERN
      --exclude-backups      exclude backup and lock files
      --exclude-caches       exclude contents of directories containing
                             CACHEDIR.TAG, except for the tag file itself
      --exclude-caches-all   exclude directories containing CACHEDIR.TAG
      --exclude-caches-under exclude everything under directories containing
                             CACHEDIR.TAG
      --exclude-ignore=FILE  read exclude patterns for each directory from
                             FILE, if it exists
      --exclude-ignore-recursive=FILE
                             read exclude patterns for each directory and its
                             subdirectories from FILE, if it exists
      --exclude-tag=FILE     exclude contents of directories containing FILE,
                             except for FILE itself
      --exclude-tag-all=FILE exclude directories containing FILE
      --exclude-tag-under=FILE   exclude everything under directories
                             containing FILE
      --exclude-vcs          exclude version control system directories
      --exclude-vcs-ignores  read exclude patterns from the VCS ignore files
      --no-null              disable the effect of the previous --null option
      --no-recursion         avoid descending automatically in directories
      --no-unquote           do not unquote input file or member names
      --no-verbatim-files-from   -T treats file names starting with dash as
                             options (default)
      --null                 -T reads null-terminated names; implies
                             --verbatim-files-from
      --recursion            recurse into directories (default)
  -T, --files-from=FILE      get names to extract or create from FILE
      --unquote              unquote input file or member names (default)
      --verbatim-files-from  -T reads file names verbatim (no escape or option
                             handling)
  -X, --exclude-from=FILE    exclude patterns listed in FILE

 File name matching options (affect both exclude and include patterns):

      --anchored             patterns match file name start
      --ignore-case          ignore case
      --no-anchored          patterns match after any '/' (default for
                             exclusion)
      --no-ignore-case       case sensitive matching (default)
      --no-wildcards         verbatim string matching
      --no-wildcards-match-slash   wildcards do not match '/'
      --wildcards            use wildcards (default for exclusion)
      --wildcards-match-slash   wildcards match '/' (default for exclusion)

 Overwrite control:

      --keep-directory-symlink   preserve existing symlinks to directories when
                             extracting
      --keep-newer-files     don't replace existing files that are newer than
                             their archive copies
  -k, --keep-old-files       don't replace existing files when extracting,
                             treat them as errors
      --no-overwrite-dir     preserve metadata of existing directories
      --one-top-level[=DIR]  create a subdirectory to avoid having loose files
                             extracted
      --overwrite            overwrite existing files when extracting
      --overwrite-dir        overwrite metadata of existing directories when
                             extracting (default)
      --recursive-unlink     empty hierarchies prior to extracting directory
      --remove-files         remove files after adding them to the archive
      --skip-old-files       don't replace existing files when extracting,
                             silently skip over them
  -U, --unlink-first         remove each file prior to extracting over it
  -W, --verify               attempt to verify the archive after writing it

 Select output stream:

      --ignore-command-error ignore exit codes of children
      --no-ignore-command-error   treat non-zero exit codes of children as
                             error
  -O, --to-stdout            extract files to standard output
      --to-command=COMMAND   pipe extracted files to another program

 Handling of file attributes:

      --atime-preserve[=METHOD]   preserve access times on dumped files, either
                             by restoring the times after reading
                             (METHOD='replace'; default) or by not setting the
                             times in the first place (METHOD='system')
      --clamp-mtime          only set time when the file is more recent than
                             what was given with --mtime
      --delay-directory-restore   delay setting modification times and
                             permissions of extracted directories until the end
                             of extraction
      --group=NAME           force NAME as group for added files
      --group-map=FILE       use FILE to map file owner GIDs and names
      --mode=CHANGES         force (symbolic) mode CHANGES for added files
      --mtime=DATE-OR-FILE   set mtime for added files from DATE-OR-FILE
  -m, --touch                don't extract file modified time
      --no-delay-directory-restore
                             cancel the effect of --delay-directory-restore
                             option
      --no-same-owner        extract files as yourself (default for ordinary
                             users)
      --no-same-permissions  apply the user's umask when extracting permissions
                             from the archive (default for ordinary users)
      --numeric-owner        always use numbers for user/group names
      --owner=NAME           force NAME as owner for added files
      --owner-map=FILE       use FILE to map file owner UIDs and names
  -p, --preserve-permissions, --same-permissions
                             extract information about file permissions
                             (default for superuser)
      --same-owner           try extracting files with the same ownership as
                             exists in the archive (default for superuser)
      --sort=ORDER           directory sorting order: none (default), name or
                             inode
  -s, --preserve-order, --same-order
                             member arguments are listed in the same order as
                             the files in the archive

 Handling of extended file attributes:

      --acls                 Enable the POSIX ACLs support
      --no-acls              Disable the POSIX ACLs support
      --no-selinux           Disable the SELinux context support
      --no-xattrs            Disable extended attributes support
      --selinux              Enable the SELinux context support
      --xattrs               Enable extended attributes support
      --xattrs-exclude=MASK  specify the exclude pattern for xattr keys
      --xattrs-include=MASK  specify the include pattern for xattr keys

 Device selection and switching:

      --force-local          archive file is local even if it has a colon
  -f, --file=ARCHIVE         use archive file or device ARCHIVE
  -F, --info-script=NAME, --new-volume-script=NAME
                             run script at end of each tape (implies -M)
  -L, --tape-length=NUMBER   change tape after writing NUMBER x 1024 bytes
  -M, --multi-volume         create/list/extract multi-volume archive
      --rmt-command=COMMAND  use given rmt COMMAND instead of rmt
      --rsh-command=COMMAND  use remote COMMAND instead of rsh
      --volno-file=FILE      use/update the volume number in FILE

 Device blocking:

  -b, --blocking-factor=BLOCKS   BLOCKS x 512 bytes per record
  -B, --read-full-records    reblock as we read (for 4.2BSD pipes)
  -i, --ignore-zeros         ignore zeroed blocks in archive (means EOF)
      --record-size=NUMBER   NUMBER of bytes per record, multiple of 512

 Archive format selection:

  -H, --format=FORMAT        create archive of the given format

 FORMAT is one of the following:
    gnu                      GNU tar 1.13.x format
    oldgnu                   GNU format as per tar <= 1.12
    pax                      POSIX 1003.1-2001 (pax) format
    posix                    same as pax
    ustar                    POSIX 1003.1-1988 (ustar) format
    v7                       old V7 tar format

      --old-archive, --portability
                             same as --format=v7
      --pax-option=keyword[[:]=value][,keyword[[:]=value]]...
                             control pax keywords
      --posix                same as --format=posix
  -V, --label=TEXT           create archive with volume name TEXT; at
                             list/extract time, use TEXT as a globbing pattern
                             for volume name

 Compression options:

  -a, --auto-compress        use archive suffix to determine the compression
                             program
  -I, --use-compress-program=PROG
                             filter through PROG (must accept -d)
  -j, --bzip2                filter the archive through bzip2
  -J, --xz                   filter the archive through xz
      --lzip                 filter the archive through lzip
      --lzma                 filter the archive through xz
      --lzop                 filter the archive through lzop
      --no-auto-compress     do not use archive suffix to determine the
                             compression program
      --zstd                 filter the archive through zstd
  -z, --gzip, --gunzip, --ungzip   filter the archive through gzip
  -Z, --compress, --uncompress   filter the archive through compress

 Local file selection:

      --backup[=CONTROL]     backup before removal, choose version CONTROL
      --hard-dereference     follow hard links; archive and dump the files they
                             refer to
  -h, --dereference          follow symlinks; archive and dump the files they
                             point to
  -K, --starting-file=MEMBER-NAME
                             begin at member MEMBER-NAME when reading the
                             archive
      --newer-mtime=DATE     compare date and time when data changed only
  -N, --newer=DATE-OR-FILE, --after-date=DATE-OR-FILE
                             only store files newer than DATE-OR-FILE
      --one-file-system      stay in local file system when creating archive
  -P, --absolute-names       don't strip leading '/'s from file names
      --suffix=STRING        backup before removal, override usual suffix ('~'
                             unless overridden by environment variable
                             SIMPLE_BACKUP_SUFFIX)

 File name transformations:

      --strip-components=NUMBER   strip NUMBER leading components from file
                             names on extraction
      --transform=EXPRESSION, --xform=EXPRESSION
                             use sed replace EXPRESSION to transform file
                             names

 Informative output:

      --checkpoint[=NUMBER]  display progress messages every NUMBERth record
                             (default 10)
      --checkpoint-action=ACTION   execute ACTION on each checkpoint
      --full-time            print file time to its full resolution
      --index-file=FILE      send verbose output to FILE
  -l, --check-links          print a message if not all links are dumped
      --no-quote-chars=STRING   disable quoting for characters from STRING
      --quote-chars=STRING   additionally quote characters from STRING
      --quoting-style=STYLE  set name quoting style; see below for valid STYLE
                             values
  -R, --block-number         show block number within archive with each message
                            
      --show-defaults        show tar defaults
      --show-omitted-dirs    when listing or extracting, list each directory
                             that does not match search criteria
      --show-snapshot-field-ranges
                             show valid ranges for snapshot-file fields
      --show-transformed-names, --show-stored-names
                             show file or archive names after transformation
      --totals[=SIGNAL]      print total bytes after processing the archive;
                             with an argument - print total bytes when this
                             SIGNAL is delivered; Allowed signals are: SIGHUP,
                             SIGQUIT, SIGINT, SIGUSR1 and SIGUSR2; the names
                             without SIG prefix are also accepted
      --utc                  print file modification times in UTC
  -v, --verbose              verbosely list files processed
      --warning=KEYWORD      warning control
  -w, --interactive, --confirmation
                             ask for confirmation for every action

 Compatibility options:

  -o                         when creating, same as --old-archive; when
                             extracting, same as --no-same-owner

 Other options:

  -?, --help                 give this help list
      --restrict             disable use of some potentially harmful options
      --usage                give a short usage message
      --version              print program version

Mandatory or optional arguments to long options are also mandatory or optional
for any corresponding short options.

The backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.
The version control may be set with --backup or VERSION_CONTROL, values are:

  none, off       never make backups
  t, numbered     make numbered backups
  nil, existing   numbered if numbered backups exist, simple otherwise
  never, simple   always make simple backups

Valid arguments for the --quoting-style option are:

  literal
  shell
  shell-always
  shell-escape
  shell-escape-always
  c
  c-maybe
  escape
  locale
  clocale

*This* tar defaults to:
--format=gnu -f- -b20 --quoting-style=escape --rmt-command=/usr/sbin/rmt
--rsh-command=/usr/bin/rsh

帮助末尾的默认值行值得划重点:*This* tar defaults to: --format=gnu -f- -b20 ...——默认归档格式 gnu、默认归档目标是 -(stdin/stdout)、块因子 20(10240 B 记录)。3.2 节漏写 -f 的三种结局、4.1 节管道搬运,都建立在 -f- 这个默认值上。

2.3 版本确认

确认实测环境的 tar 版本:

$ tar --version
tar (GNU tar) 1.34
Copyright (C) 2021 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <https://gnu.org/licenses/gpl.html>.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

Written by John Gilmore and Jay Fenlason.

三、参数逐项实测

3.1 三种主模式 -c/-t/-x 与 -f 指定归档文件

GNU tar 围绕「一个主操作 + -f 指定归档」组织:-c 创建、-t 列出、-x 解包。先创建 demo.tar 并用 -t 验证内容:

$ tar -cf demo.tar project

$ ls -l demo.tar
-rw-r--r-- 1 root root 2641920 Oct 15 18:46 demo.tar

$ tar -tf demo.tar
project/
project/src/
project/src/util.py
project/src/main.c
project/src/access.log
project/config.ini
project/Makefile
project/docs/
project/docs/readme.md

解读:-tf 列出 9 个条目,按递归遍历顺序排列,目录条目(project/、project/src/、project/docs/)以 / 结尾各占一行。demo.tar 为 2641920 B——正好是 2580 个 10240 B 记录(默认块因子 20),tar 归档按记录对齐,尾部以全零块收束。

💡 要点:-f 是 tar 少数必须显式给参数的选项;默认 -f- 指向标准输入/输出(2.2 末尾默认值行),漏写 -f 时行为因输出去向而异,见 3.2 节。

3.2 漏写 -f 的三种结局

忘写 -f 是 tar 最高频的笔误,GNU tar 1.34 下有三种截然不同的结局,逐一实测:

$ tar -cf; echo rc=$?
tar: option requires an argument -- 'f'
Try 'tar --help' or 'tar --usage' for more information.
rc=64

$ timeout 10 script -qec "tar -c project" /dev/null; echo rc=$?
tar: Refusing to write archive contents to terminal (missing -f option?)
tar: Error is not recoverable: exiting now
rc=2

$ tar -c project > stdout-arch.tar; ls -l stdout-arch.tar demo.tar; rm -f stdout-arch.tar
-rw-r--r-- 1 root root 2641920 Oct 15 18:46 demo.tar
-rw-r--r-- 1 root root 2641920 Oct 15 18:46 stdout-arch.tar

解读:第一种 -cf 缺参数,直接 usage error 退出 rc=64;第二种带主操作但无 -f,tar 检测到 stdout 连着终端,拒绝把二进制归档灌进终端 rc=2;第三种 stdout 被重定向到文件,tar 按默认 -f- 把归档写进 stdout-arch.tar——产物与显式 -f 的 demo.tar 逐字节同大(2641920 B)。

💡 要点:脚本里务必显式写 -f。rc=64 只是缺参数;真正的静默风险在无终端防护的场合(cron、CI、管道末端),归档会悄悄写进 stdout 落到意想不到的地方。

3.3 -v 过程回显:长列表与创建清单

-v 在 -t 时输出权限/属主/大小/时间的完整列表,在 -c 时逐行回显处理的条目:

$ tar -tvf demo.tar
drwxr-xr-x root/root         0 2026-10-15 18:46 project/
drwxr-xr-x root/root         0 2026-10-15 18:46 project/src/
-rw-r--r-- root/root       372 2026-10-15 18:46 project/src/util.py
-rw-r--r-- root/root       435 2026-10-15 18:46 project/src/main.c
-rw-r--r-- root/root   2628894 2026-10-15 18:46 project/src/access.log
-rw-r--r-- root/root        96 2026-10-15 18:46 project/config.ini
-rw-r--r-- root/root       113 2026-10-15 18:46 project/Makefile
drwxr-xr-x root/root         0 2026-10-15 18:46 project/docs/
-rw-r--r-- root/root       164 2026-10-15 18:46 project/docs/readme.md

$ rm -f demo.tar

$ tar -cvf demo.tar project
project/
project/src/
project/src/util.py
project/src/main.c
project/src/access.log
project/config.ini
project/Makefile
project/docs/
project/docs/readme.md

解读:-tvf 一行一个条目,字段依次为权限、属主/组、字节数、修改时间、条目名;-cvf 的回显清单与 -tf 完全一致。目录条目大小恒为 0(tar 只记录目录元数据)。

💡 要点:-tvf 是解包前的标准预检动作——先看清条目前缀(有无 ./ 或绝对路径)再决定解包策略(3.4、3.5 节)。

3.4 绝对路径剥离与 -C 切换目录

打包绝对路径时 tar 会剥离前导 / 并告警;配合 -C 可切换工作目录,用 . 打包目录内容得到 ./ 前缀:

$ tar -cf abs.tar /root/demo/tar/project
tar: Removing leading `/' from member names

$ tar -tf abs.tar | head -6
root/demo/tar/project/
root/demo/tar/project/src/
root/demo/tar/project/src/util.py
root/demo/tar/project/src/main.c
root/demo/tar/project/src/access.log
root/demo/tar/project/config.ini

$ tar -czf demo.tgz -C /root/demo/tar/project .

$ tar -tzf demo.tgz
./
./src/
./src/util.py
./src/main.c
./src/access.log
./config.ini
./Makefile
./docs/
./docs/readme.md

解读:Removing leading '/' 是保护机制——防止解包时覆盖同名系统文件,条目变成相对路径 root/demo/tar/project/...(要保留绝对路径须显式 -P,见 2.2 帮助 Local file selection 一节);-C 目录 . 产生 ./ 前缀,解包时文件直接散落在目标目录(3.5 节 ex2 实证)。

💡 要点:想让解包「原地散开」就用 -C 目录 . 打包;想解包后仍带一层目录壳就按目录名打包。前缀形态决定解包后的目录结构,发布包选前者、备份包选后者。

3.5 解包控制:-C 指定目录、--wildcards 与 --strip-components

-x 的落点由 -C 指定;配合 --wildcards 可按模式只解部分条目,--strip-components 可在解包时剥离前导层级:

$ rm -rf ex1 && mkdir ex1

$ tar -xf demo.tar -C ex1

$ find ex1 -type f | sort
ex1/project/Makefile
ex1/project/config.ini
ex1/project/docs/readme.md
ex1/project/src/access.log
ex1/project/src/main.c
ex1/project/src/util.py

$ rm -rf ex2 && mkdir ex2

$ tar -xf demo.tgz -C ex2

$ find ex2 -type f | sort
ex2/Makefile
ex2/config.ini
ex2/docs/readme.md
ex2/src/access.log
ex2/src/main.c
ex2/src/util.py

按模式与按层级两种精细解包(--wildcards 从 demo.tar.gz 解,--strip-components 从 demo.tar 解):

$ rm -rf ex-wild && mkdir ex-wild

$ tar -xzf demo.tar.gz -C ex-wild --wildcards "project/src/*.py"

$ find ex-wild -type f | sort
ex-wild/project/src/util.py

$ tar -cf demo.tar project

$ rm -rf ex-strip && mkdir ex-strip

$ tar -xf demo.tar -C ex-strip --strip-components=1 project/config.ini project/src/util.py

$ find ex-strip -type f | sort
ex-strip/config.ini
ex-strip/src/util.py

解读:demo.tar 条目带 project/ 前缀所以解出 ex1/project/...,demo.tgz 是 ./ 前缀所以直接散在 ex2 根下——印证 3.4 节的前缀结论。--wildcards "project/src/*.py" 只解出 util.py 一个条目;--strip-components=1 把 project/ 这一层剥掉,config.ini 直接落在 ex-strip 根下,且只解命令行点名的 2 个条目。

💡 要点:--strip-components 只在解包侧生效、按层级数剥离且不改归档本身,同一包可按不同层级反复解;剥多了不同子目录的文件会「叠」到同一位置互相覆盖。

3.6 压缩过滤 -z/-j/-J 与 -a 自动选择

-z/-j/-J 分别在归档流外层接 gzip/bzip2/xz;-a 按输出文件后缀自动挑选压缩程序:

$ tar -czf demo.tar.gz project

$ tar -cjf demo.tar.bz2 project

$ tar -cJf demo.tar.xz project

$ ls -l demo.tar demo.tar.gz demo.tar.bz2 demo.tar.xz
-rw-r--r-- 1 root root 2641920 Oct 15 18:46 demo.tar
-rw-r--r-- 1 root root   32387 Oct 15 18:46 demo.tar.bz2
-rw-r--r-- 1 root root   86075 Oct 15 18:46 demo.tar.gz
-rw-r--r-- 1 root root   28324 Oct 15 18:46 demo.tar.xz

$ tar -tzf demo.tar.gz
project/
project/src/
project/src/util.py
project/src/main.c
project/src/access.log
project/config.ini
project/Makefile
project/docs/
project/docs/readme.md

-a 按后缀自动选择压缩程序,用 file 确认产物格式:

$ tar -caf auto.tar.gz project/config.ini

$ file auto.tar.gz
auto.tar.gz: gzip compressed data, from Unix, original size modulo 2^32 10240

$ tar -caf auto.tar.xz project/config.ini

$ file auto.tar.xz
auto.tar.xz: XZ compressed data, checksum CRC64

$ tar -caf auto.tar.bz2 project/config.ini

$ file auto.tar.bz2
auto.tar.bz2: bzip2 compressed data, block size = 900k

$ tar -tf auto.tar.xz; echo rc=$?
project/config.ini
rc=0

解读:同一数据三种压缩 2641920 → 86075(gz)/ 32387(bz2)/ 28324(xz),xz 最小、gzip 最快,负载为高重复日志文本时差距显著。-a 生成的三个 auto.tar.* 经 file 确认格式正确,且 -tf 不带任何压缩参数即可直接列表——读取端按魔数自动识别压缩格式。

💡 技术拓展:tar 本身只打包,-z/-j/-J 外接的压缩算法才决定体积与耗时,常见选择横向对比——

算法特点适用场景
gzip(-z)deflate 算法,速度最快、生态最广默认选择,日志与软件包分发
bzip2(-j)BWT 算法,压缩率高于 gzip,速度明显慢冷数据归档,不赶时间
xz(-J)LZMA2 算法,压缩率最高,内存开销最大发布源码包,追求最小体积
zstd(--zstd)压缩率接近 xz 而速度快一个量级高吞吐场景(大数据/内核包)

本篇 3.6 实测中 xz 体积最小(28,324 B)、gzip 最快;日常分发优先 gzip,极限压缩选 xz,兼顾体积与速度的新趋势是 zstd。

💡 要点:-a 依赖后缀识别,写成 auto.tar.dat 会退化为纯 tar;-t/-x 读取端无需压缩参数,-z/-j/-J 写给创建端即可,解包端写了也不报错但属冗余。

3.7 --exclude 排除与 --exclude-vcs 版本控制目录

--exclude 按 PATTERN 剔除条目(可重复出现);--exclude-vcs 一次性排除 .git 等版本控制目录:

$ tar -czf excl.tar.gz --exclude="*.log" --exclude="*.md" project

$ tar -tzf excl.tar.gz
project/
project/src/
project/src/util.py
project/src/main.c
project/config.ini
project/Makefile
project/docs/

$ mkdir -p fakeproj/.git && echo "[core]" > fakeproj/.git/config && echo hello > fakeproj/a.txt

$ tar -czf novcs.tar.gz fakeproj

$ tar -tzf novcs.tar.gz
fakeproj/
fakeproj/a.txt
fakeproj/.git/
fakeproj/.git/config

$ tar --exclude-vcs -czf novcs2.tar.gz fakeproj

$ tar -tzf novcs2.tar.gz
fakeproj/
fakeproj/a.txt

解读:两个 --exclude 后清单少了 src/access.log 与 docs/readme.md 两个文件条目;fakeproj 里的 .git/ 及其内容默认照收,--exclude-vcs 后只剩业务文件。

💡 要点:排除模式默认「非锚定」(2.2 帮助原文 --no-anchored ... default for exclusion)——--exclude="*.log" 匹配任意层级的 .log 文件;要限定在路径开头匹配需配合 --anchored。

3.8 追加 -r 与压缩档限制

未压缩 tar 支持 -r 就地追加:

$ tar -cf add.tar project/config.ini

$ tar -rf add.tar project/Makefile

$ tar -tf add.tar
project/config.ini
project/Makefile

$ tar -rf demo.tar.gz project/docs/readme.md; echo rc=$?
tar: Cannot update compressed archives
tar: Error is not recoverable: exiting now
rc=2

解读:add.tar 可连续 -r 追加,-tf 按追加顺序列出;对 gzip 压缩档 -r 直接失败 rc=2——压缩流不可随机寻址,无法就地拼接。

💡 要点:归档维护三件套 -r/-u/--delete 只对未压缩 tar 有效;压缩包要改内容,只能解包重建或「先 -xf 成 tar、改完再压缩」。

3.9 -d 比对归档与磁盘

-d 逐条目比较归档副本与磁盘文件:

$ tar -df demo.tar project; echo rc=$?
rc=0

$ sleep 2

$ echo "# appended for tar -d test" >> project/config.ini

$ tar -df demo.tar project; echo rc=$?
project/config.ini: Mod time differs
project/config.ini: Size differs
rc=1

解读:初始无差异 rc=0;向 config.ini 追加一行(96 B → 123 B)后,-d 精确指出 Mod time 与 Size 两项差异并返回 rc=1。这是校验备份与现网一致性的直接手段。

💡 要点:-d 只比对命令行列出的文件参数(这里给了整个 project/),不比对归档里的孤立条目;它只读不改,配合 3.10 的 -u 形成「先比对、后增量」工作流。

3.10 更新 -u、删除 --delete 与 --remove-files

-u 只追加「比归档副本新」的文件(承接 3.9 节的修改),--delete 删除条目,--remove-files 打包后删除源文件:

$ tar -uf add.tar project/config.ini

$ tar -tf add.tar
project/config.ini
project/Makefile
project/config.ini

$ tar -df add.tar project; echo rc=$?
project/config.ini: Mod time differs
project/config.ini: Size differs
rc=1

$ tar --delete -f add.tar project/config.ini

$ tar -tf add.tar
project/Makefile
$ cp -r project scratch

$ tar -czf scratch.tar.gz --remove-files scratch

$ ls -d scratch; echo rc=$?
ls: cannot access 'scratch': No such file or directory
rc=2

$ tar -tzf scratch.tar.gz | head -3
scratch/
scratch/src/
scratch/src/util.py

解读:-u 对已变化的 config.ini 追加了一份新副本——-tf 出现两条同名条目,旧副本仍在所以 -d 依旧 rc=1;--delete 把两条同名条目一并移除;--remove-files 打包 scratch 后源目录被删除,ls -d 报 No such file(rc=2 是 ls 的),归档内容完整。

💡 要点:-u 是「追加新副本」而非「原地替换」,归档体积只增不减,要瘦身只能 --delete 后重建;--remove-files 谨慎用于自动化——出错时源文件已被移走,只剩归档可救。

3.11 权限与属主:-p / --same-owner / --numeric-owner

tar 在条目里记录权限与属主元数据,解包时的恢复策略由一组参数控制:

$ chmod 600 project/config.ini

$ tar -cf perm.tar project/config.ini

$ tar -tvf perm.tar
-rw------- root/root       123 2026-10-15 18:46 project/config.ini

$ tar -tvf perm.tar --numeric-owner
-rw------- 0/0             123 2026-10-15 18:46 project/config.ini

$ rm -rf ex-perm && mkdir ex-perm

$ tar -xf perm.tar -C ex-perm

$ ls -l ex-perm/project/config.ini
-rw------- 1 root root 123 Oct 15 18:46 ex-perm/project/config.ini

$ tar --same-owner -xf perm.tar -C ex-perm

$ tar -xpf perm.tar -C ex-perm

$ ls -l ex-perm/project/config.ini
-rw------- 1 root root 123 Oct 15 18:46 ex-perm/project/config.ini

解读:chmod 600 后打包,-tvf 忠实记录 -rw-------;--numeric-owner 把 root/root 显示为 0/0(条目里本来就存数字 UID/GID,此参数只是让列表按数字显示);root 身份解包默认按归档恢复权限(等效 -p,帮助原文 default for superuser)与属主(--same-owner 为 root 默认),ls 确认解出文件保持 600。本节后该文件保持 600,直到实验序列末尾统一恢复 644。

💡 要点:普通用户解包默认 --no-same-owner/--no-same-permissions——属主变成自己、权限过 umask;跨机迁移核对 UID 时用 --numeric-owner,避免名称映射不一致掩盖属主漂移。

3.12 -O 解包到标准输出

-O 把指定条目内容直接写到 stdout,不落盘:

$ tar -cf o.tar project/config.ini

$ tar -xf o.tar -O; echo rc=$?
[server]
host = 127.0.0.1
port = 8080
timeout = 30

[log]
level = info
file = /var/log/demo.log
# appended for tar -d test
rc=0

$ rm -f o.tar

解读:-O 输出的 9 行正是 config.ini 的完整内容(末行 # appended for tar -d test 证明 o.tar 打包于 3.9 节追加之后),rc=0。指定条目名时只输出该条目,不产生任何文件。

💡 要点:-O 常用于「不落地读归档内文件」——cat 查看、md5sum 校验或管道二次处理;省略条目名则全部条目灌进 stdout,注意与重定向方向搭配。

3.13 -h 打包时跟随符号链接

默认打包把软链接存为链接条目,-h 改为存目标文件内容:

$ ln -s project/config.ini cfg-link

$ tar -cf link.tar cfg-link

$ tar -tvf link.tar
lrwxrwxrwx root/root         0 2026-10-15 18:57 cfg-link -> project/config.ini

$ tar -chf deref.tar cfg-link

$ tar -tvf deref.tar
-rw-r--r-- root/root       123 2026-10-15 18:46 cfg-link

$ rm -f link.tar deref.tar cfg-link

解读:默认打包的 cfg-link 存为 lrwxrwxrwx 链接条目(大小 0、-> 指向目标路径字符串);加 -h 后存成 -rw-r--r-- 123 B 的普通文件(123 B 为 3.9 节追加后的目标文件大小,条目时间也是目标文件的 18:46)。

💡 要点:-h 会把同一目标的多个链接实体化成多份冗余内容,且解包端得到的是实体文件;分发目录树给不方便还原链接的接收方时用 -h,备份原目录结构时保留默认。

3.14 进度与统计:--checkpoint 与 --totals

--checkpoint 周期性输出进度,--totals 收尾统计写入字节数:

$ rm -f cp.tar

$ tar -cf cp.tar --checkpoint=100 project
tar: Write checkpoint 100
tar: Write checkpoint 200

$ tar -cf totals.tar --totals project/config.ini project/Makefile
Total bytes written: 10240 (10KiB, 96MiB/s)

$ rm -f totals.tar

解读:--checkpoint=100 每写 100 个记录(100 × 10240 B)输出一行 Write checkpoint,打包 project 共 200+ 个记录触发两次;--totals 打印 Total bytes written: 10240——两个小文件加 tar 头部与记录填充,对齐到 10 KiB(一个默认记录),速率 96MiB/s 也一并给出。

💡 要点:默认 checkpoint 间隔为 10(2.2 帮助原文 default 10),大归档调到百级可减少刷屏;--totals 是核对「写进去了多少」的最快手段,不必再 ls -l 换算。

四、实战示例

4.1 管道搬运目录:不落地的复制

目录树搬运的经典惯用法——左端 -C 切换后以 . 打包写入 stdout(-f -),右端子 shell cd 后从 stdin 解包:

$ rm -rf dest && mkdir dest

$ tar -C /root/demo/tar/project -cf - . | (cd /root/demo/tar/dest && tar -xf -)

$ diff -r project dest; echo rc=$?
rc=0

$ ls dest
Makefile
config.ini
docs
src

解读:diff -r 逐文件比对 rc=0,目标目录与源完全一致;归档流只经过管道、不在磁盘落地,-C 两端各自定位,-f - 显式指定 stdin/stdout。

💡 要点:把管道中段换成 ssh 目标机 'tar -xf -' 就是跨机搬运;数据量大或需断点续传时 rsync 更合适,小体量目录树一次成型用 tar 管道最干净。

4.2 覆盖控制:-xfk 粘连陷阱与 --overwrite

解包遇到同名文件有四种策略:默认覆盖、-k 报错、--skip-old-files 静默跳过、--keep-newer-files 保守保留。实测最常用的 -k 与 --overwrite,包括一个选项粘连的经典陷阱:

$ rm -rf ex-k && mkdir ex-k

$ tar -xf demo.tar -C ex-k

$ echo modified > ex-k/project/config.ini

$ tar -xfk demo.tar -C ex-k; echo rc=$?
tar: k: Cannot open: No such file or directory
tar: Error is not recoverable: exiting now
rc=2

$ tar -xkf demo.tar -C ex-k; echo rc=$?
tar: project/src/util.py: Cannot open: File exists
tar: project/src/main.c: Cannot open: File exists
tar: project/src/access.log: Cannot open: File exists
tar: project/config.ini: Cannot open: File exists
tar: project/Makefile: Cannot open: File exists
tar: project/docs/readme.md: Cannot open: File exists
tar: Exiting with failure status due to previous errors
rc=2

$ tar -xf demo.tar -C ex-k --overwrite; echo rc=$?
rc=0

$ cat ex-k/project/config.ini
[server]
host = 127.0.0.1
port = 8080
timeout = 30

[log]
level = info
file = /var/log/demo.log

解读:第一条 -xfk 里 tar 把粘连组中 f 之后的 k 当成了归档文件名(tar: k: Cannot open)——单破折号短选项组里只有需要参数的那个字母能带值,其后字母全被吞进参数;正确写法 -xkf 才把 k 解析为 keep-old-files,对 6 个已存在文件逐条报 File exists rc=2;--overwrite 显式覆盖后 rc=0,cat 确认 config.ini 内容回到归档中的原始版本(3.9 节追加的行消失——demo.tar 打包于追加之前)。

💡 要点:-f 带参数的短选项参与粘连时必须放在组尾,或者干脆全部分开写 -x -f 归档 -k;--skip-old-files/--keep-newer-files 见 2.2 帮助 Overwrite control 一节,适合增量落地场景。

4.3 损坏归档排障:截断 tar 的表现

传输中断最常产生截断归档。取 demo.tar 前 4096 字节模拟,观察 -t 与 -x 的表现:

$ head -c 4096 demo.tar > broken.tar

$ tar -tf broken.tar; echo rc=$?
project/
project/src/
project/src/util.py
project/src/main.c
project/src/access.log
tar: rmtlseek not stopped at a record boundary
tar: Error is not recoverable: exiting now
rc=2

$ rm -rf ex-bad && mkdir ex-bad

$ tar -xf broken.tar -C ex-bad; echo rc=$?
tar: Unexpected EOF in archive
tar: rmtlseek not stopped at a record boundary
tar: Error is not recoverable: exiting now
rc=2

解读:-t 读出前 5 个条目后才在记录边界失步处报错 rc=2——已列出的条目仍然可信;-x 同样先解出能解的部分、再报 Unexpected EOF in archive rc=2。

💡 要点:tar 没有类似 zip 中央目录的尾部索引,完整性只看尾部两块全零结束标记——截断即失效,但头部条目可抢救;发布前用 tar -tf 全量列表或 sha256sum 校验兜底。

五、注意事项与常见问题

Q1:tar 命令漏写 -f 到底会发生什么? 分三种情况(3.2 节实测):-cf 这种缺参数写法直接 usage error rc=64;tar -c 目录 在终端场合会被拒绝写终端 rc=2;stdout 被重定向时按默认 -f- 把归档写进重定向目标。GNU tar 1.34 的默认归档目标就是标准输入/输出(2.2 节默认值行),所以「老教材说不加 -f 用磁带设备」在 GNU tar 上不成立。

Q2:为什么 -r/-u/--delete 对 .tar.gz 报 "Cannot update compressed archives"? 压缩流不可随机寻址,就地追加/删除无从谈起(3.8 节 rc=2 实测)。替代方案:未压缩的 tar 先维护再压缩;或解包到临时目录重建归档。归档维护三件套 -r/-u/--delete 只对未压缩 tar 有效。

Q3:tar -u 更新后 -tf 出现两条同名条目,正常吗? 正常。-u 的实现是「把较新的文件追加到归档尾部」,不删除旧副本(3.10 节实测两条 config.ini),-d 会继续报旧副本差异,体积只增不减。要得到「只留一份」的归档,用 --delete 删同名条目或干脆重建。

Q4:解包会覆盖已有文件吗?如何安全解包? 默认覆盖(4.2 节 --overwrite 后内容被替换)。保护策略按需选:-k/--keep-old-files 遇同名报错停止、--skip-old-files 静默跳过、--keep-newer-files 只在归档副本较新时覆盖、-U/--unlink-first 先删再解。落到生产脚本建议先 -tvf 预检(3.3 节)再解到独立目录(-C + mkdir)。

Q5:截断的 tar 还能救回数据吗? 部分可以。tar 无中央目录,-t/-x 都是顺序读取,实测截断到 4096 B 时 -t 仍列出前 5 个条目、-x 解出对应文件后以 rc=2 终止(4.3 节)——条目在截断点之前的部分完好。预防优于抢救:发布前 tar -tf 校验、传输配 sha256sum、超大归档用分卷。

六、参考链接