daemon-sec-cheatsheet

The cheatsheet vault for operators: AD, enumeration, exploitation, priv-esc, web, DFIR
git clone https://git.daemon-sec.xyz/daemon-sec-cheatsheet.git
Log | Files | Refs | README | LICENSE

tar.md (33351B)


      1 ---
      2 title: "TAR"
      3 description: "tar [OPERATION] [OPTIONS] -f ARCHIVE [FILES...]"
      4 category: tools
      5 tags: ["tools"]
      6 tools: ["GPG", "OpenSSL"]
      7 difficulty: intermediate
      8 updated: "2026-08-10"
      9 source: "vault:Tools/TAR.md"
     10 ---
     11 # The Ultimate `tar` Cheat Sheet
     12 
     13 > GNU tar 1.35 | Last updated: April 2026
     14 
     15 ---
     16 
     17 ## Core Syntax
     18 
     19 ```
     20 tar [OPERATION] [OPTIONS] -f ARCHIVE [FILES...]
     21 ```
     22 
     23 The `-f` flag tells tar you're working with files, not a tape device (the `f` is always required).
     24 
     25 ---
     26 
     27 ## Operations (pick one)
     28 
     29 |Flag|Long Form|Purpose|
     30 |---|---|---|
     31 |`c`|`--create`|Create a new archive|
     32 |`x`|`--extract`|Extract files from an archive|
     33 |`t`|`--list`|List contents of an archive|
     34 |`r`|`--append`|Append files to the end of an archive (uncompressed only)|
     35 |`u`|`--update`|Append files newer than the copy in the archive (uncompressed only)|
     36 |`d`|`--diff` / `--compare`|Compare archive members against the filesystem|
     37 |`A`|`--concatenate`|Append one tar archive to another|
     38 |`--delete`||Delete members from the archive (uncompressed only)|
     39 
     40 ---
     41 
     42 ## Common Modifier Flags
     43 
     44 |Flag|Long Form|Purpose|
     45 |---|---|---|
     46 |`v`|`--verbose`|Verbose output (list files processed)|
     47 |`vv`||Extra verbose (show permissions, ownership, size)|
     48 |`f`|`--file`|Specify archive filename|
     49 |`C`|`--directory`|Change to directory before performing operation|
     50 |`p`|`--preserve-permissions`|Preserve file permissions on extraction|
     51 |`P`|`--absolute-names`|Don't strip leading `/` from paths|
     52 |`k`|`--keep-old-files`|Don't overwrite existing files on extraction|
     53 |`--overwrite`||Overwrite existing files on extraction|
     54 |`w`|`--interactive`|Ask for confirmation for every action|
     55 |`--same-owner`||Try to extract with the same ownership|
     56 |`--no-same-owner`||Extract as current user|
     57 |`--numeric-owner`||Use numeric UID/GID (useful for cross-system restores)|
     58 |`--acls`||Preserve POSIX ACLs|
     59 |`--selinux`||Preserve SELinux contexts|
     60 |`--xattrs`||Preserve extended attributes|
     61 |`-h`|`--dereference`|Follow symlinks (archive the target file, not the link)|
     62 |`--hard-dereference`||Follow hard links|
     63 |`--one-file-system`||Stay on one filesystem (don't cross mount points)|
     64 |`-S`|`--sparse`|Handle sparse files efficiently|
     65 |`--totals`||Print total bytes after processing|
     66 |`--checkpoint=N`||Print a progress message every N records|
     67 
     68 ---
     69 
     70 ## All Compression Types
     71 
     72 GNU tar supports 8 compression filters. Each can be used with `-c` (create) or `-x` (extract).
     73 
     74 ### Quick Reference
     75 
     76 |Algorithm|Short Flag|Long Flag|Extension|Compression Ratio|Speed|Levels|
     77 |---|---|---|---|---|---|---|
     78 |gzip|`-z`|`--gzip`|`.tar.gz` / `.tgz`|Good|Fast|1-9|
     79 |bzip2|`-j`|`--bzip2`|`.tar.bz2` / `.tbz2`|Better|Slow|1-9|
     80 |xz (LZMA2)|`-J`|`--xz`|`.tar.xz` / `.txz`|Best|Slowest|0-9 (+`-e` extreme)|
     81 |zstd|`--zstd`|`--zstd`|`.tar.zst`|Good-Great|Very Fast|1-19 (+`--ultra` to 22)|
     82 |lzip|`--lzip`|`--lzip`|`.tar.lz`|Best|Slow|0-9|
     83 |lzma|`--lzma`|`--lzma`|`.tar.lzma`|Great|Slow|0-9|
     84 |lzop|`--lzop`|`--lzop`|`.tar.lzo`|Lower|Very Fast|1-9|
     85 |compress|`-Z`|`--compress`|`.tar.Z`|Poor|Fast|N/A (legacy)|
     86 
     87 ### Create with Each Compression Type
     88 
     89 ```bash
     90 # gzip (most universal)
     91 tar czf archive.tar.gz /path/to/dir
     92 
     93 # bzip2 (legacy, still common in older tarballs)
     94 tar cjf archive.tar.bz2 /path/to/dir
     95 
     96 # xz (best compression, used by kernel tarballs and distro packages)
     97 tar cJf archive.tar.xz /path/to/dir
     98 
     99 # zstd (best speed-to-ratio balance, modern default)
    100 tar --zstd -cf archive.tar.zst /path/to/dir
    101 
    102 # lzip
    103 tar --lzip -cf archive.tar.lz /path/to/dir
    104 
    105 # lzma (predecessor to xz)
    106 tar --lzma -cf archive.tar.lzma /path/to/dir
    107 
    108 # lzop (ultrafast, low ratio)
    109 tar --lzop -cf archive.tar.lzo /path/to/dir
    110 
    111 # compress (legacy, avoid)
    112 tar -Zcf archive.tar.Z /path/to/dir
    113 
    114 # no compression (plain tarball)
    115 tar cf archive.tar /path/to/dir
    116 ```
    117 
    118 ### Extract with Each Type
    119 
    120 ```bash
    121 # gzip
    122 tar xzf archive.tar.gz
    123 
    124 # bzip2
    125 tar xjf archive.tar.bz2
    126 
    127 # xz
    128 tar xJf archive.tar.xz
    129 
    130 # zstd
    131 tar --zstd -xf archive.tar.zst
    132 
    133 # lzip
    134 tar --lzip -xf archive.tar.lz
    135 
    136 # auto-detect compression (GNU tar)
    137 tar xaf archive.tar.gz    # 'a' auto-detects the compressor
    138 tar xf archive.tar.xz     # GNU tar also auto-detects without 'a' in most cases
    139 ```
    140 
    141 ### List Contents Without Extracting
    142 
    143 ```bash
    144 tar tzf archive.tar.gz                 # gzip
    145 tar tjf archive.tar.bz2                # bzip2
    146 tar tJf archive.tar.xz                 # xz
    147 tar --zstd -tf archive.tar.zst         # zstd
    148 tar tf archive.tar                     # uncompressed
    149 tar tf archive.tar.gz | head -20       # preview first 20 entries
    150 tar tf archive.tar.gz | grep '\\.conf$' # search for .conf files
    151 ```
    152 
    153 ---
    154 
    155 ## Setting Compression Levels
    156 
    157 The `-I` flag (or `--use-compress-program`) lets you pass custom arguments to the compressor, including compression levels. This is how you control the speed/size tradeoff.
    158 
    159 ### Method 1: The `-I` Flag (Recommended)
    160 
    161 ```bash
    162 # gzip: levels 1 (fastest) to 9 (smallest), default 6
    163 tar -I 'gzip -1' -cf archive.tar.gz /path/to/dir     # fastest
    164 tar -I 'gzip -6' -cf archive.tar.gz /path/to/dir     # default
    165 tar -I 'gzip -9' -cf archive.tar.gz /path/to/dir     # smallest
    166 
    167 # bzip2: levels 1-9, default 9
    168 tar -I 'bzip2 -1' -cf archive.tar.bz2 /path/to/dir   # fastest
    169 tar -I 'bzip2 -9' -cf archive.tar.bz2 /path/to/dir   # smallest (default)
    170 
    171 # xz: levels 0-9, default 6. Add -e for extreme mode
    172 tar -I 'xz -0' -cf archive.tar.xz /path/to/dir       # fastest
    173 tar -I 'xz -6' -cf archive.tar.xz /path/to/dir       # default
    174 tar -I 'xz -9' -cf archive.tar.xz /path/to/dir       # smallest
    175 tar -I 'xz -9e' -cf archive.tar.xz /path/to/dir      # extreme (even smaller, much slower)
    176 
    177 # zstd: levels 1-19, default 3. --ultra unlocks 20-22
    178 tar -I 'zstd -1' -cf archive.tar.zst /path/to/dir     # fastest
    179 tar -I 'zstd -3' -cf archive.tar.zst /path/to/dir     # default
    180 tar -I 'zstd -19' -cf archive.tar.zst /path/to/dir    # high compression
    181 tar -I 'zstd --ultra -22' -cf archive.tar.zst /path/to/dir  # maximum (memory heavy)
    182 
    183 # lzip: levels 0-9, default 6
    184 tar -I 'lzip -9' -cf archive.tar.lz /path/to/dir
    185 
    186 # lzop: levels 1-9, default 3
    187 tar -I 'lzop -9' -cf archive.tar.lzo /path/to/dir
    188 ```
    189 
    190 ### Method 2: Environment Variables (gzip/bzip2/xz only)
    191 
    192 ```bash
    193 # gzip via GZIP env var (deprecated in newer gzip, but still works in most distros)
    194 GZIP=-9 tar czf archive.tar.gz /path/to/dir
    195 
    196 # xz via XZ_OPT
    197 XZ_OPT='-9e' tar cJf archive.tar.xz /path/to/dir
    198 
    199 # zstd via ZSTD_CLEVEL
    200 ZSTD_CLEVEL=19 tar --zstd -cf archive.tar.zst /path/to/dir
    201 ```
    202 
    203 ---
    204 
    205 ## Multi-threaded / Parallel Compression
    206 
    207 Single-threaded compression is painfully slow on large datasets. Use parallel implementations to utilise all your cores.
    208 
    209 ### Native Multi-threading
    210 
    211 ```bash
    212 # xz with -T0 (use all available cores, supported since xz 5.2+)
    213 tar -I 'xz -9e -T0' -cf archive.tar.xz /path/to/dir
    214 
    215 # zstd with -T0 (native multi-threading, default since zstd 1.5.7)
    216 tar -I 'zstd -19 -T0' -cf archive.tar.zst /path/to/dir
    217 
    218 # zstd with explicit thread count
    219 tar -I 'zstd -19 -T4' -cf archive.tar.zst /path/to/dir
    220 ```
    221 
    222 ### Drop-in Parallel Replacements
    223 
    224 ```bash
    225 # pigz (parallel gzip, fully compatible output)
    226 tar -I 'pigz -9' -cf archive.tar.gz /path/to/dir
    227 tar -I 'pigz -9 -p 4' -cf archive.tar.gz /path/to/dir    # limit to 4 cores
    228 
    229 # pbzip2 (parallel bzip2)
    230 tar -I 'pbzip2 -9' -cf archive.tar.bz2 /path/to/dir
    231 tar -I 'pbzip2 -9 -p4' -cf archive.tar.bz2 /path/to/dir
    232 
    233 # lbzip2 (alternative parallel bzip2, often faster decompression)
    234 tar -I lbzip2 -cf archive.tar.bz2 /path/to/dir
    235 
    236 # plzip (parallel lzip)
    237 tar -I 'plzip -9' -cf archive.tar.lz /path/to/dir
    238 ```
    239 
    240 ### Install Parallel Tools
    241 
    242 ```bash
    243 # Debian/Ubuntu
    244 sudo apt install pigz pbzip2 lbzip2 zstd
    245 
    246 # RHEL/Fedora
    247 sudo dnf install pigz pbzip2 lbzip2 zstd
    248 
    249 # Arch
    250 sudo pacman -S pigz pbzip2 lbzip2 zstd
    251 
    252 # macOS
    253 brew install pigz pbzip2 lbzip2 zstd
    254 ```
    255 
    256 ---
    257 
    258 ## Splitting Archives into Parts
    259 
    260 For transferring over networks, fitting onto FAT32 drives (4GB limit), or uploading in chunks.
    261 
    262 ### Create and Split in One Pipeline
    263 
    264 ```bash
    265 # Split a gzip archive into 100MB chunks
    266 tar czf - /path/to/dir | split -b 100M - archive.tar.gz.part-
    267 
    268 # Split an xz archive into 500MB chunks with numeric suffixes
    269 tar cJf - /path/to/dir | split -b 500M -d - archive.tar.xz.part-
    270 
    271 # Split a zstd archive into 1GB chunks
    272 tar --zstd -cf - /path/to/dir | split -b 1G -d - archive.tar.zst.part-
    273 
    274 # Split with a custom number of digits in suffix
    275 tar czf - /path/to/dir | split -b 100M -d -a 3 - archive.tar.gz.part-
    276 # produces: archive.tar.gz.part-000, archive.tar.gz.part-001, ...
    277 ```
    278 
    279 ### Split an Existing Archive
    280 
    281 ```bash
    282 split -b 100M archive.tar.gz archive.tar.gz.part-
    283 ```
    284 
    285 ### Reassemble and Extract
    286 
    287 ```bash
    288 # Reassemble into a single file, then extract
    289 cat archive.tar.gz.part-* > archive.tar.gz
    290 tar xzf archive.tar.gz
    291 
    292 # Or pipe directly without creating the intermediate file
    293 cat archive.tar.gz.part-* | tar xzf -
    294 
    295 # For xz
    296 cat archive.tar.xz.part-* | tar xJf -
    297 
    298 # For zstd
    299 cat archive.tar.zst.part-* | tar --zstd -xf -
    300 ```
    301 
    302 ### Verify Split Archive Integrity
    303 
    304 ```bash
    305 # Check the reassembled archive is valid
    306 cat archive.tar.gz.part-* | tar tzf - > /dev/null && echo "OK" || echo "CORRUPT"
    307 
    308 # Generate checksums before transfer
    309 sha256sum archive.tar.gz.part-* > checksums.sha256
    310 
    311 # Verify after transfer
    312 sha256sum -c checksums.sha256
    313 ```
    314 
    315 ### GNU tar Native Multi-Volume (`-M`)
    316 
    317 ```bash
    318 # Create multi-volume archive (each volume max 100MB)
    319 tar -cML 100M -f vol1.tar /path/to/dir
    320 # tar will prompt for the next volume name when vol1 fills up
    321 
    322 # Extract multi-volume
    323 tar -xMf vol1.tar
    324 # tar prompts for subsequent volumes
    325 
    326 # Note: multi-volume archives CANNOT be compressed
    327 # For compressed split archives, use the pipe method above
    328 ```
    329 
    330 ---
    331 
    332 ## Excluding Files and Directories
    333 
    334 ```bash
    335 # Exclude a single file or directory
    336 tar czf archive.tar.gz --exclude='*.log' /path/to/dir
    337 
    338 # Exclude multiple patterns
    339 tar czf archive.tar.gz \\
    340   --exclude='*.log' \\
    341   --exclude='*.tmp' \\
    342   --exclude='.git' \\
    343   --exclude='node_modules' \\
    344   --exclude='__pycache__' \\
    345   /path/to/dir
    346 
    347 # Exclude from a file (one pattern per line)
    348 tar czf archive.tar.gz --exclude-from=exclude.txt /path/to/dir
    349 
    350 # Exclude files matching a regex (GNU tar)
    351 tar czf archive.tar.gz --exclude='./src/*.test.js' /path/to/dir
    352 
    353 # Exclude version control directories
    354 tar czf archive.tar.gz --exclude-vcs /path/to/dir
    355 # Excludes: .git, .svn, .hg, .bzr, CVS, etc.
    356 
    357 # Exclude version control ignores too (.gitignore, .hgignore, etc.)
    358 tar czf archive.tar.gz --exclude-vcs-ignores /path/to/dir
    359 
    360 # Exclude backup files (*~, #*#)
    361 tar czf archive.tar.gz --exclude-backups /path/to/dir
    362 
    363 # Exclude files if a certain file exists in the directory
    364 tar czf archive.tar.gz --exclude-tag='.nobackup' /path/to/dir
    365 
    366 # Exclude caches (directories containing CACHEDIR.TAG)
    367 tar czf archive.tar.gz --exclude-caches /path/to/dir
    368 ```
    369 
    370 ---
    371 
    372 ## Extracting Specific Files
    373 
    374 ```bash
    375 # Extract a single file
    376 tar xzf archive.tar.gz path/to/specific/file.txt
    377 
    378 # Extract files matching a wildcard
    379 tar xzf archive.tar.gz --wildcards '*.conf'
    380 tar xzf archive.tar.gz --wildcards '*/nginx/*'
    381 
    382 # Extract to a specific directory
    383 tar xzf archive.tar.gz -C /opt/restore/
    384 
    385 # Extract only newer files (don't overwrite newer existing files)
    386 tar xzf archive.tar.gz --keep-newer-files
    387 
    388 # Extract and strip leading path components
    389 tar xzf archive.tar.gz --strip-components=1
    390 # e.g. project-v1.0/src/main.c extracts as src/main.c
    391 
    392 tar xzf archive.tar.gz --strip-components=2
    393 # e.g. project-v1.0/src/main.c extracts as main.c
    394 ```
    395 
    396 ---
    397 
    398 ## Incremental / Differential Backups
    399 
    400 GNU tar supports incremental backups using a snapshot file that tracks filesystem state between runs.
    401 
    402 ```bash
    403 # Level 0: full backup (creates the snapshot file)
    404 tar -g /backup/snapshot.snar -czf /backup/full-$(date +%F).tar.gz /home/user/
    405 
    406 # Level 1: incremental (only files changed since the last backup)
    407 tar -g /backup/snapshot.snar -czf /backup/inc-$(date +%F).tar.gz /home/user/
    408 
    409 # To force a new full backup, delete or move the snapshot file
    410 rm /backup/snapshot.snar
    411 
    412 # Restore: apply full, then each incremental IN ORDER
    413 tar -xzf /backup/full-2026-03-01.tar.gz -g /dev/null -C /restore/
    414 tar -xzf /backup/inc-2026-03-02.tar.gz -g /dev/null -C /restore/
    415 tar -xzf /backup/inc-2026-03-03.tar.gz -g /dev/null -C /restore/
    416 # Note: -g /dev/null on extract tells tar this is an incremental restore
    417 # and it should handle file deletions properly
    418 ```
    419 
    420 ---
    421 
    422 ## Encryption
    423 
    424 tar has no native encryption. Pipe through `gpg` or `openssl` to encrypt.
    425 
    426 ### With GPG (Symmetric / Passphrase)
    427 
    428 ```bash
    429 # Create encrypted archive (prompts for passphrase)
    430 tar czf - /path/to/dir | gpg -c --cipher-algo AES256 -o archive.tar.gz.gpg
    431 
    432 # Decrypt and extract
    433 gpg -d archive.tar.gz.gpg | tar xzf -
    434 
    435 # With a specific recipient's public key (asymmetric)
    436 tar czf - /path/to/dir | gpg -e -r recipient@example.com -o archive.tar.gz.gpg
    437 
    438 # Decrypt (requires matching private key)
    439 gpg -d archive.tar.gz.gpg | tar xzf -
    440 ```
    441 
    442 ### With OpenSSL
    443 
    444 ```bash
    445 # Encrypt with AES-256-CBC (prompts for password)
    446 tar czf - /path/to/dir | openssl enc -aes-256-cbc -salt -pbkdf2 -out archive.tar.gz.enc
    447 
    448 # Decrypt and extract
    449 openssl enc -d -aes-256-cbc -pbkdf2 -in archive.tar.gz.enc | tar xzf -
    450 ```
    451 
    452 ### Encrypted Incremental Backup (GPG + tar)
    453 
    454 ```bash
    455 # Full backup, encrypted
    456 tar -g snapshot.snar -czf - /home/user/ | gpg -c --cipher-algo AES256 -o backup-full.tar.gz.gpg
    457 
    458 # Incremental, encrypted
    459 tar -g snapshot.snar -czf - /home/user/ | gpg -c --cipher-algo AES256 -o backup-inc.tar.gz.gpg
    460 
    461 # Restore
    462 gpg -d backup-full.tar.gz.gpg | tar -xzf - -g /dev/null -C /restore/
    463 gpg -d backup-inc.tar.gz.gpg | tar -xzf - -g /dev/null -C /restore/
    464 ```
    465 
    466 ### Encrypted + Split
    467 
    468 ```bash
    469 # Create, compress, encrypt, and split into 100MB chunks
    470 tar czf - /path/to/dir \\
    471   | gpg -c --cipher-algo AES256 \\
    472   | split -b 100M -d - archive.tar.gz.gpg.part-
    473 
    474 # Reassemble, decrypt, extract
    475 cat archive.tar.gz.gpg.part-* | gpg -d | tar xzf -
    476 ```
    477 
    478 ---
    479 
    480 ## Sending Archives Over the Network
    481 
    482 ```bash
    483 # Archive and transfer via SSH in one step
    484 tar czf - /path/to/dir | ssh user@remote 'cat > /backup/archive.tar.gz'
    485 
    486 # Extract remotely
    487 tar czf - /path/to/dir | ssh user@remote 'tar xzf - -C /opt/deploy/'
    488 
    489 # Pull from remote
    490 ssh user@remote 'tar czf - /remote/dir' | tar xzf - -C /local/restore/
    491 
    492 # With zstd for speed
    493 tar --zstd -cf - /path/to/dir | ssh user@remote 'tar --zstd -xf - -C /opt/deploy/'
    494 
    495 # With progress bar (requires pv)
    496 tar cf - /path/to/dir | pv | gzip | ssh user@remote 'cat > /backup/archive.tar.gz'
    497 
    498 # Encrypted transfer (belt and braces with SSH)
    499 tar czf - /path/to/dir | gpg -c --cipher-algo AES256 | ssh user@remote 'cat > /backup/archive.tar.gz.gpg'
    500 ```
    501 
    502 ---
    503 
    504 ## Comparing and Verifying Archives
    505 
    506 ```bash
    507 # Diff: compare archive members against the filesystem
    508 tar dzf archive.tar.gz
    509 # Shows files that differ between the archive and disk
    510 
    511 # Verify archive integrity without extracting
    512 tar tzf archive.tar.gz > /dev/null
    513 echo $?    # 0 = OK, non-zero = corrupted
    514 
    515 # Test a zstd archive
    516 tar --zstd -tf archive.tar.zst > /dev/null && echo "OK" || echo "CORRUPT"
    517 
    518 # Generate a checksum of the archive
    519 sha256sum archive.tar.gz > archive.tar.gz.sha256
    520 
    521 # Verify
    522 sha256sum -c archive.tar.gz.sha256
    523 ```
    524 
    525 ---
    526 
    527 ## Archive Formats
    528 
    529 GNU tar can produce several archive formats. Usually you don't need to worry about this, but it matters for edge cases.
    530 
    531 ```bash
    532 # Specify format explicitly
    533 tar --format=gnu -cf archive.tar /path/to/dir      # GNU format (default)
    534 tar --format=posix -cf archive.tar /path/to/dir     # POSIX.1-2001 (pax) format
    535 tar --format=ustar -cf archive.tar /path/to/dir     # POSIX.1-1988
    536 tar --format=v7 -cf archive.tar /path/to/dir        # Old Unix V7 format
    537 ```
    538 
    539 |Format|Long Filenames|Large Files (>8GB)|Extended Attributes|Notes|
    540 |---|---|---|---|---|
    541 |gnu|Yes|Yes|No|Default on Linux|
    542 |posix (pax)|Yes|Yes|Yes|Most portable, recommended for cross-platform|
    543 |ustar|255 chars max|No (8GB limit)|No|Older POSIX standard|
    544 |v7|100 chars max|No|No|Legacy, avoid|
    545 
    546 ---
    547 
    548 ## Practical Combos and Recipes
    549 
    550 ### Full System Backup
    551 
    552 ```bash
    553 tar -I 'zstd -9 -T0' -cpf /backup/system-$(date +%F).tar.zst \\
    554   --acls --selinux --xattrs \\
    555   --one-file-system \\
    556   --exclude='/proc/*' \\
    557   --exclude='/sys/*' \\
    558   --exclude='/dev/*' \\
    559   --exclude='/run/*' \\
    560   --exclude='/tmp/*' \\
    561   --exclude='/mnt/*' \\
    562   --exclude='/media/*' \\
    563   --exclude='/lost+found' \\
    564   --exclude='/backup/*' \\
    565   /
    566 ```
    567 
    568 ### Web Server Backup
    569 
    570 ```bash
    571 tar -I 'zstd -12 -T0' -cf /backup/webserver-$(date +%F).tar.zst \\
    572   --exclude='*.log' \\
    573   --exclude='cache/*' \\
    574   --exclude='node_modules' \\
    575   /etc/nginx /etc/letsencrypt /var/www
    576 ```
    577 
    578 ### Quick Grab of Specific File Types
    579 
    580 ```bash
    581 # Archive only .py files from a project
    582 find /project -name '*.py' -print0 | tar czf python-files.tar.gz --null -T -
    583 
    584 # Archive files modified in the last 24 hours
    585 find /path -mtime -1 -print0 | tar czf recent-changes.tar.gz --null -T -
    586 
    587 # Archive from a file list
    588 tar czf archive.tar.gz -T filelist.txt
    589 ```
    590 
    591 ### Benchmark Compression Algorithms
    592 
    593 ```bash
    594 for alg in 'gzip' 'bzip2' 'xz' 'zstd' 'zstd -19' 'xz -9e'; do
    595   echo "--- $alg ---"
    596   time tar -I "$alg" -cf /dev/null /path/to/test/dir 2>&1
    597   echo
    598 done
    599 ```
    600 
    601 ### Disk Image Compression
    602 
    603 ```bash
    604 # Compress a raw disk image with zstd
    605 dd if=/dev/sda bs=4M status=progress | zstd -T0 > disk-image.zst
    606 
    607 # Restore
    608 zstd -d disk-image.zst | dd of=/dev/sda bs=4M status=progress
    609 ```
    610 
    611 ### Progress Bar with `pv`
    612 
    613 ```bash
    614 # Show progress while creating
    615 tar cf - /large/directory | pv -s $(du -sb /large/directory | cut -f1) | gzip > archive.tar.gz
    616 
    617 # Show progress while extracting
    618 pv archive.tar.gz | tar xzf -
    619 ```
    620 
    621 ---
    622 
    623 ## Gotchas and Limitations
    624 
    625 - **Compressed archives cannot be modified.** You cannot use `--update`, `--append`, or `--delete` on `.tar.gz`, `.tar.xz`, etc. Only uncompressed `.tar` files support these operations.
    626 - **Multi-volume archives cannot be compressed.** Use the `split` pipe method instead.
    627 - **Leading `/` is stripped by default.** This is a safety feature. Use `-P` to preserve absolute paths, but be careful on extraction.
    628 - **Sparse file handling** requires `-S` to be passed explicitly.
    629 - **Cross-platform gotchas:** GNU tar extensions (long filenames, ACLs, xattrs) may not be understood by BSD tar or busybox tar. Use `--format=posix` for maximum portability.
    630 - **File ordering is not guaranteed** unless you sort your input file list.
    631 - **xz multi-threaded compression uses a lot of RAM.** Roughly single-thread memory x thread count. Watch out on memory-constrained systems.
    632 
    633 ---
    634 
    635 ## When to Use What
    636 
    637 | Scenario                              | Algorithm       | Reasoning                                        |
    638 | ------------------------------------- | --------------- | ------------------------------------------------ |
    639 | Daily backups                         | zstd (`-3 -T0`) | Fast, good ratio, multi-threaded by default      |
    640 | Long-term archival                    | xz (`-9e -T0`)  | Best compression ratio, saves storage            |
    641 | Quick one-off / maximum compatibility | gzip            | Available everywhere, fast enough                |
    642 | Software distribution                 | xz              | Standard for kernel tarballs, distro packages    |
    643 | Real-time / filesystem compression    | zstd            | Used by btrfs, Fedora, Ubuntu, Arch for packages |
    644 | Bandwidth-limited transfer            | xz or zstd -19  | Minimise bytes on the wire                       |
    645 | Speed-critical / huge datasets        | lzop or zstd -1 | Minimal CPU overhead                             |
    646 | Legacy systems / old tarballs         | bzip2           | Superseded but still encountered                 |
    647 
    648 ---
    649 
    650 ## Portability: `-I` vs `--use-compress-program` vs Explicit Pipes
    651 
    652 The `-I` flag behaves **differently** between GNU tar and BSD tar (macOS default). This is one of the most common causes of confusing errors.
    653 
    654 ### The Problem
    655 
    656 |Platform|`-I` Means|
    657 |---|---|
    658 |GNU tar (Linux)|`--use-compress-program` — run this external compressor|
    659 |BSD tar / bsdtar (macOS)|`--include` — include files matching a pattern (same as `-T`)|
    660 
    661 So on macOS: `tar -I 'xz -9e' -cf ...` will fail with `Couldn't open xz -9e: No such file or directory` because BSD tar is trying to read a **file list** called `xz -9e`.
    662 
    663 ### Three Ways to Handle It
    664 
    665 ```bash
    666 # Method 1: --use-compress-program (works on BOTH GNU and BSD tar)
    667 tar -c --use-compress-program='xz -9e' -f - /path/to/dir > archive.tar.xz
    668 
    669 # Method 2: Explicit pipe (most portable, works EVERYWHERE)
    670 tar cf - /path/to/dir | xz -9e > archive.tar.xz
    671 
    672 # Method 3: -I flag (GNU tar ONLY — Linux, not macOS)
    673 tar -I 'xz -9e' -cf archive.tar.xz /path/to/dir
    674 ```
    675 
    676 ### Portable Decompression (Explicit Pipe)
    677 
    678 ```bash
    679 # These work on any system regardless of tar implementation
    680 xz -d < archive.tar.xz | tar xf -
    681 zstd -d < archive.tar.zst | tar xf -
    682 gzip -d < archive.tar.gz | tar xf -
    683 ```
    684 
    685 ### Check Which tar You Have
    686 
    687 ```bash
    688 tar --version
    689 # GNU tar 1.35  → you have GNU tar, -I works as compress program
    690 # bsdtar 3.x.x  → you have BSD tar, -I means --include, use pipes instead
    691 ```
    692 
    693 > **Rule of thumb:** If your script needs to run on both Linux and macOS, always use explicit pipes (`tar cf - | compressor`) or `--use-compress-program`. Never rely on `-I`.
    694 
    695 ---
    696 
    697 ## Chained Workflows & Pipeline Recipes
    698 
    699 The real power of tar comes from chaining it with other Unix tools via pipes. Since tar can write to stdout (`-f -`) and read from stdin (`-f -`), you can build arbitrarily complex pipelines: **compress → encrypt → split → checksum → transfer** — all in a single streaming operation with no intermediate files hitting disk.
    700 
    701 ### The Pipeline Building Blocks
    702 
    703 ```
    704 ┌──────┐    ┌────────────┐    ┌──────────┐    ┌───────┐    ┌──────────┐
    705 │ tar  │───▶│ compressor │───▶│ encryptor│───▶│ split │───▶│ checksum │
    706 │ -cf -│    │ zstd/xz/gz │    │ gpg/age  │    │       │    │ sha256   │
    707 └──────┘    └────────────┘    └──────────┘    └───────┘    └──────────┘
    708 ```
    709 
    710 Each block is optional. Mix and match depending on what you need.
    711 
    712 ---
    713 
    714 ### Compress + Split (Custom Levels)
    715 
    716 When tar's built-in `-z`/`-J`/`--zstd` flags don't let you set a compression level, break the compressor out into its own pipe stage.
    717 
    718 ```bash
    719 # xz extreme + multi-threaded + split into 1GB parts
    720 tar cf - /path/to/dir \\
    721   | xz -9e -T0 \\
    722   | split -b 1G -d -a 3 - archive.tar.xz.part-
    723 
    724 # Reassemble and extract
    725 cat archive.tar.xz.part-* | xz -d | tar xf -
    726 
    727 # zstd level 19 + multi-threaded + split into 500MB parts
    728 tar cf - /path/to/dir \\
    729   | zstd -19 -T0 \\
    730   | split -b 500M -d -a 3 - archive.tar.zst.part-
    731 
    732 # Reassemble and extract
    733 cat archive.tar.zst.part-* | zstd -d | tar xf -
    734 
    735 # pigz (parallel gzip) level 9 + split into 100MB parts
    736 tar cf - /path/to/dir \\
    737   | pigz -9 \\
    738   | split -b 100M -d - archive.tar.gz.part-
    739 
    740 # Reassemble and extract
    741 cat archive.tar.gz.part-* | pigz -d | tar xf -
    742 ```
    743 
    744 ---
    745 
    746 ### Compress + Encrypt + Split (The Full Chain)
    747 
    748 The order matters: **always compress before encrypting**. Encrypted data is random and cannot be compressed further.
    749 
    750 ```bash
    751 # ── With GPG (symmetric) ──
    752 tar cf - /path/to/dir \\
    753   | zstd -19 -T0 \\
    754   | gpg -c --cipher-algo AES256 --batch --passphrase-fd 3 3<<<'YourPassphrase' \\
    755   | split -b 500M -d -a 3 - archive.tar.zst.gpg.part-
    756 
    757 # Reassemble, decrypt, decompress, extract
    758 cat archive.tar.zst.gpg.part-* \\
    759   | gpg -d --batch --passphrase 'YourPassphrase' \\
    760   | zstd -d \\
    761   | tar xf -
    762 
    763 # ── With GPG (asymmetric / public key) ──
    764 tar cf - /path/to/dir \\
    765   | xz -9e -T0 \\
    766   | gpg -e -r recipient@example.com \\
    767   | split -b 1G -d -a 3 - archive.tar.xz.gpg.part-
    768 
    769 # Recipient reassembles, decrypts, extracts
    770 cat archive.tar.xz.gpg.part-* | gpg -d | xz -d | tar xf -
    771 
    772 # ── With age (modern GPG alternative, simpler) ──
    773 tar cf - /path/to/dir \\
    774   | zstd -19 -T0 \\
    775   | age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p \\
    776   > archive.tar.zst.age
    777 
    778 # Decrypt and extract
    779 age -d -i key.txt archive.tar.zst.age | zstd -d | tar xf -
    780 
    781 # ── With age + split ──
    782 tar cf - /path/to/dir \\
    783   | zstd -19 -T0 \\
    784   | age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p \\
    785   | split -b 500M -d -a 3 - archive.tar.zst.age.part-
    786 
    787 cat archive.tar.zst.age.part-* | age -d -i key.txt | zstd -d | tar xf -
    788 
    789 # ── With OpenSSL ──
    790 tar cf - /path/to/dir \\
    791   | xz -9e -T0 \\
    792   | openssl enc -aes-256-cbc -salt -pbkdf2 \\
    793   | split -b 500M -d -a 3 - archive.tar.xz.enc.part-
    794 
    795 cat archive.tar.xz.enc.part-* \\
    796   | openssl enc -d -aes-256-cbc -pbkdf2 \\
    797   | xz -d \\
    798   | tar xf -
    799 ```
    800 
    801 ---
    802 
    803 ### Compress + Split + Checksum (Integrity Verification)
    804 
    805 Generate checksums for each split part so you can verify after transfer.
    806 
    807 ```bash
    808 # Create, compress, split, then checksum
    809 tar cf - /path/to/dir | zstd -19 -T0 | split -b 500M -d -a 3 - archive.tar.zst.part-
    810 sha256sum archive.tar.zst.part-* > archive.tar.zst.sha256
    811 
    812 # After transfer, verify
    813 sha256sum -c archive.tar.zst.sha256
    814 
    815 # Or inline: tee into sha256sum while splitting
    816 tar cf - /path/to/dir \\
    817   | zstd -19 -T0 \\
    818   | tee >(sha256sum > archive-whole.sha256) \\
    819   | split -b 500M -d -a 3 - archive.tar.zst.part-
    820 ```
    821 
    822 ---
    823 
    824 ### Compress + Encrypt + Split + Checksum (Full Paranoia Pipeline)
    825 
    826 ```bash
    827 # ── CREATE ──
    828 tar cf - /path/to/dir \\
    829   | zstd -19 -T0 \\
    830   | gpg -c --cipher-algo AES256 \\
    831   | split -b 500M -d -a 3 - archive.tar.zst.gpg.part-
    832 
    833 # Checksum all parts
    834 sha256sum archive.tar.zst.gpg.part-* > checksums.sha256
    835 
    836 # ── VERIFY + RESTORE ──
    837 sha256sum -c checksums.sha256 && \\
    838 cat archive.tar.zst.gpg.part-* | gpg -d | zstd -d | tar xf - -C /restore/
    839 ```
    840 
    841 ---
    842 
    843 ### Compress + Progress Bar + Split
    844 
    845 Use `pv` (pipe viewer) to monitor progress at any stage of the pipeline.
    846 
    847 ```bash
    848 # Show progress while compressing and splitting
    849 tar cf - /path/to/dir \\
    850   | pv -s $(du -sb /path/to/dir | cut -f1) -N "tar" \\
    851   | zstd -19 -T0 \\
    852   | pv -N "zstd" \\
    853   | split -b 500M -d -a 3 - archive.tar.zst.part-
    854 
    855 # Show progress while reassembling and extracting
    856 cat archive.tar.zst.part-* \\
    857   | pv -N "reassemble" \\
    858   | zstd -d \\
    859   | tar xf - -C /restore/
    860 
    861 # Progress bar with encryption
    862 tar cf - /path/to/dir \\
    863   | pv -s $(du -sb /path/to/dir | cut -f1) \\
    864   | zstd -19 -T0 \\
    865   | gpg -c --cipher-algo AES256 \\
    866   > archive.tar.zst.gpg
    867 
    868 # pv -W (wait) is useful when piping into gpg since gpg prompts for a
    869 # passphrase before processing — -W delays the progress bar until data flows
    870 tar cf - /path/to/dir \\
    871   | zstd -19 -T0 \\
    872   | pv -W \\
    873   | gpg -c --cipher-algo AES256 \\
    874   > archive.tar.zst.gpg
    875 ```
    876 
    877 ---
    878 
    879 ### Compress + Network Transfer (SSH)
    880 
    881 Stream directly to a remote host — nothing touches local disk except the source.
    882 
    883 ```bash
    884 # ── Push: local → remote (zstd, multi-threaded) ──
    885 tar cf - /path/to/dir \\
    886   | zstd -19 -T0 \\
    887   | ssh user@remote 'zstd -d | tar xf - -C /opt/deploy/'
    888 
    889 # ── Push: local → remote (save as file on remote) ──
    890 tar cf - /path/to/dir \\
    891   | zstd -19 -T0 \\
    892   | ssh user@remote 'cat > /backup/archive.tar.zst'
    893 
    894 # ── Pull: remote → local ──
    895 ssh user@remote 'tar cf - /remote/dir | zstd -T0' \\
    896   | zstd -d \\
    897   | tar xf - -C /local/restore/
    898 
    899 # ── Push with progress ──
    900 tar cf - /path/to/dir \\
    901   | pv -s $(du -sb /path/to/dir | cut -f1) \\
    902   | zstd -T0 \\
    903   | ssh user@remote 'zstd -d | tar xf - -C /opt/deploy/'
    904 
    905 # ── Clone directory between hosts (one-liner) ──
    906 ssh user@source 'tar cf - /data | zstd -T0' \\
    907   | ssh user@dest 'zstd -d | tar xf - -C /'
    908 ```
    909 
    910 ---
    911 
    912 ### Compress + Network Transfer (Netcat — No SSH Overhead)
    913 
    914 Fastest possible transfer on a trusted LAN. No encryption, no SSH overhead.
    915 
    916 ```bash
    917 # ── Receiver (start first) ──
    918 nc -l -p 9000 | zstd -d | tar xf - -C /restore/
    919 
    920 # ── Sender ──
    921 tar cf - /path/to/dir | zstd -T0 | nc receiver-host 9000
    922 
    923 # ── With progress on sender side ──
    924 tar cf - /path/to/dir \\
    925   | pv -s $(du -sb /path/to/dir | cut -f1) \\
    926   | zstd -T0 \\
    927   | nc receiver-host 9000
    928 
    929 # ── With inline checksum verification ──
    930 # Sender (prints md5 to stderr after transfer)
    931 tar cf - /path/to/dir | zstd -T0 | tee >(md5sum >&2) | nc receiver-host 9000
    932 
    933 # Receiver (prints md5 to stderr after receiving)
    934 nc -l -p 9000 | tee >(md5sum >&2) | zstd -d | tar xf - -C /restore/
    935 # Compare the two md5 hashes — they should match
    936 ```
    937 
    938 ---
    939 
    940 ### Compress + Encrypt + Network Transfer
    941 
    942 ```bash
    943 # ── Push encrypted archive over SSH ──
    944 tar cf - /path/to/dir \\
    945   | zstd -19 -T0 \\
    946   | gpg -c --cipher-algo AES256 \\
    947   | ssh user@remote 'cat > /backup/archive.tar.zst.gpg'
    948 
    949 # ── Pull, decrypt, extract in one shot ──
    950 ssh user@remote 'cat /backup/archive.tar.zst.gpg' \\
    951   | gpg -d \\
    952   | zstd -d \\
    953   | tar xf - -C /local/restore/
    954 
    955 # ── Netcat + encryption (for untrusted networks without SSH) ──
    956 # Receiver:
    957 nc -l -p 9000 | gpg -d | zstd -d | tar xf - -C /restore/
    958 
    959 # Sender:
    960 tar cf - /path/to/dir | zstd -T0 | gpg -c --cipher-algo AES256 | nc receiver-host 9000
    961 ```
    962 
    963 ---
    964 
    965 ### Incremental Backup + Compress + Encrypt + Split
    966 
    967 Full automated backup pipeline with incrementals.
    968 
    969 ```bash
    970 SNAP="/backup/snapshot.snar"
    971 DATE=$(date +%F)
    972 
    973 # ── Full backup (first run or when snapshot is deleted) ──
    974 tar -g "$SNAP" -cf - /home/user \\
    975   | zstd -19 -T0 \\
    976   | gpg -c --cipher-algo AES256 \\
    977   | split -b 1G -d -a 3 - "/backup/full-${DATE}.tar.zst.gpg.part-"
    978 sha256sum /backup/full-${DATE}.tar.zst.gpg.part-* > "/backup/full-${DATE}.sha256"
    979 
    980 # ── Incremental backup (subsequent runs) ──
    981 tar -g "$SNAP" -cf - /home/user \\
    982   | zstd -12 -T0 \\
    983   | gpg -c --cipher-algo AES256 \\
    984   > "/backup/inc-${DATE}.tar.zst.gpg"
    985 sha256sum "/backup/inc-${DATE}.tar.zst.gpg" >> "/backup/inc-${DATE}.sha256"
    986 
    987 # ── Restore: full first, then each incremental in order ──
    988 sha256sum -c /backup/full-2026-03-01.sha256 && \\
    989 cat /backup/full-2026-03-01.tar.zst.gpg.part-* \\
    990   | gpg -d | zstd -d | tar xf - -g /dev/null -C /restore/
    991 
    992 gpg -d /backup/inc-2026-03-02.tar.zst.gpg \\
    993   | zstd -d | tar xf - -g /dev/null -C /restore/
    994 ```
    995 
    996 ---
    997 
    998 ### Exclude + Find + Compress + Encrypt (Surgical Archives)
    999 
   1000 ```bash
   1001 # Archive only files modified in last 7 days, compress with zstd, encrypt with age
   1002 find /project -mtime -7 -type f -print0 \\
   1003   | tar cf - --null -T - \\
   1004   | zstd -19 -T0 \\
   1005   | age -r age1ql3z7hjy54pw3hyww5ayyfg7zqgvc7w3j2elw8zmrj2kg5sfn9aqmcac8p \\
   1006   > recent-changes.tar.zst.age
   1007 
   1008 # Archive specific file types, exclude build artifacts, compress + split
   1009 find /project \begin{raycast-math} -name '*.py' -o -name '*.rs' -o -name '*.toml' \end{raycast-math} -print0 \\
   1010   | tar cf - --null -T - \\
   1011   --exclude='target' \\
   1012   --exclude='__pycache__' \\
   1013   | zstd -19 -T0 \\
   1014   | split -b 100M -d -a 3 - source-code.tar.zst.part-
   1015 ```
   1016 
   1017 ---
   1018 
   1019 ### Extract to Pipe (Process Each File)
   1020 
   1021 Use `--to-command` to pipe each extracted file into a program instead of writing to disk.
   1022 
   1023 ```bash
   1024 # Pipe every extracted file through a processor (e.g. wc -l to count lines)
   1025 tar xf archive.tar.gz --to-command='wc -l'
   1026 
   1027 # The filename is available inside --to-command as $TAR_FILENAME
   1028 tar xf archive.tar.gz --to-command='echo "Processing: $TAR_FILENAME"'
   1029 
   1030 # Extract and pipe each file into a script
   1031 tar xf archive.tar.gz --to-command='/path/to/your/script.sh'
   1032 ```
   1033 
   1034 ---
   1035 
   1036 ### Copy Directory Trees (Local Cloning)
   1037 
   1038 The fastest way to copy a directory preserving all metadata — faster than `cp -a` or `rsync` for local copies.
   1039 
   1040 ```bash
   1041 # Clone a directory tree preserving permissions, ownership, timestamps
   1042 tar cf - -C /source/dir . | tar xpf - -C /dest/dir
   1043 
   1044 # Same but with progress
   1045 tar cf - -C /source/dir . \\
   1046   | pv -s $(du -sb /source/dir | cut -f1) \\
   1047   | tar xpf - -C /dest/dir
   1048 
   1049 # Clone with full metadata preservation
   1050 tar cf - --acls --xattrs --selinux -C /source/dir . \\
   1051   | tar xpf - --acls --xattrs --selinux -C /dest/dir
   1052 ```
   1053 
   1054 ---
   1055 
   1056 ### Quick Reference: Pipeline Order
   1057 
   1058 When combining operations, follow this order:
   1059 
   1060 ```
   1061 CREATE → COMPRESS → ENCRYPT → SPLIT → CHECKSUM → TRANSFER
   1062 tar    → zstd/xz → gpg/age → split → sha256   → ssh/nc
   1063 ```
   1064 
   1065 And to reverse:
   1066 
   1067 ```
   1068 REASSEMBLE → VERIFY   → DECRYPT → DECOMPRESS → EXTRACT
   1069 cat        → sha256sum → gpg/age → zstd/xz    → tar
   1070 ```
   1071 
   1072 > **Key principle:** Every tool in the chain reads from stdin and writes to stdout. The pipe (`|`) connects them. Use `-f -` to tell tar to read/write stdin/stdout instead of a file.
   1073 
   1074 ---
   1075 
   1076 ### Encryption Tool Comparison for Pipelines
   1077 
   1078 | Tool | Type | Pipe-friendly | Key Management | Notes |
   1079 |------|------|:---:|---|---|
   1080 | `gpg -c` | Symmetric (passphrase) | ✅ | None needed | Universal, prompts for passphrase |
   1081 | `gpg -e -r` | Asymmetric (public key) | ✅ | Keyring required | Standard for sharing with others |
   1082 | `age -p` | Symmetric (passphrase) | ✅ | None needed | Modern, simple, no config |
   1083 | `age -r` | Asymmetric (public key) | ✅ | Single key file | No keyring, just a file |
   1084 | `openssl enc` | Symmetric (passphrase) | ✅ | None needed | Always available, more flags |
   1085 
   1086 Install `age`: `brew install age` / `sudo apt install age` / `sudo pacman -S age`