Bash and Linux

Pack and Unpack Archives

easyShell basics and files

Problem statement

Pack a folder into one compressed .tar.gz file, look inside it, unpack it somewhere else, and compress a single file with gzip. You use this to send logs to a teammate, back up a config folder before a change, ship a build, and read old rotated logs that end in .gz.

The script creates:

logs/access.log

TEXT
GET /home 200
GET /cart 200

logs/error.log

TEXT
db timeout

notes.txt

TEXT
deploy went fine

Do these steps in order:

  1. Pack the logs folder into logs.tar.gz.
  2. List what is inside the archive without unpacking it.
  3. Unpack it into a folder called restore, list the files there, and print error.log.
  4. Compress notes.txt with gzip, keeping the original, list both files, and read the compressed one without unpacking it.

Expected output:

TEXT
== inside logs.tar.gz ==
logs/
logs/access.log
logs/error.log
== unpacked files ==
restore/logs/access.log
restore/logs/error.log
== restore/logs/error.log ==
db timeout
== notes files ==
notes.txt
notes.txt.gz
== read the .gz without unpacking ==
deploy went fine

Hints

Hint 1: Read tar flags as words: c create, t list, x extract, z gzip, f "the archive file name comes next".

Approach

Optimal: tar and gzip

Covers: tar -czf, tar -tzf, tar -xzf, tar -C, gzip, gzip -k, gunzip, zcat.

Two jobs, two tools. Packing a folder is really two separate steps, and Linux uses a separate tool for each:

  • tar bundles many files and folders into one file, keeping names, folders and permissions. It does not shrink anything on its own. A plain bundle ends in .tar.
  • gzip shrinks one file. It cannot bundle a folder. A compressed file ends in .gz.

Put together, you get a .tar.gz (also written .tgz): a bundle that has been shrunk. The z flag tells tar to run gzip for you, so it is still one command.

%%{init: {"flowchart": {"padding": 18, "nodeSpacing": 30, "rankSpacing": 40, "htmlLabels": true}, "themeVariables": {"fontSize": "18px"}}}%% flowchart LR subgraph PACK["Pack: tar -czf"] direction TB F["logs/ folder"]:::blue --> T1(["tar bundles"]):::purple --> T[("logs.tar")]:::yellow --> G1(["gzip shrinks"]):::purple --> G[("logs.tar.gz")]:::green end subgraph UNPACK["Unpack: tar -xzf"] direction TB G2[("logs.tar.gz")]:::green --> X1(["gunzip, then
split the bundle"]):::purple --> R["restore/logs/"]:::blue end PACK ~~~ UNPACK classDef blue fill:#dbeafe,stroke:#2563eb,color:#1e3a8a,stroke-width:2px classDef yellow fill:#fef3c7,stroke:#d97706,color:#78350f,stroke-width:2px classDef green fill:#d1fae5,stroke:#059669,color:#064e3b,stroke-width:2px classDef red fill:#fee2e2,stroke:#dc2626,color:#7f1d1d,stroke-width:2px classDef purple fill:#ede9fe,stroke:#7c3aed,color:#4c1d95,stroke-width:2px classDef gray fill:#f3f4f6,stroke:#6b7280,color:#111827,stroke-width:2px linkStyle default stroke:#94a3b8,stroke-width:2px style PACK fill:transparent,stroke:#2563eb,stroke-width:2px style UNPACK fill:transparent,stroke:#059669,stroke-width:2px

Read the flags as words. Most people only need these:

Flag Word Meaning
c create make a new archive
t table list what is inside
x extract unpack
z gzip compress or decompress with gzip
f file the archive name is the next word
v verbose print each file as it goes
-C dir change dir go into dir before unpacking

So tar -czf logs.tar.gz logs reads as "create, gzip, file named logs.tar.gz, from the folder logs". tar -xzf logs.tar.gz -C restore reads as "extract, gzip, file named logs.tar.gz, into restore".

f must come last in the group. f takes the next word as the archive name. In -czf logs.tar.gz, the next word is logs.tar.gz, which is right. If you write -cfz logs.tar.gz, the next word after f is z, and tar tries to make an archive called z.

gzip on a single file. gzip notes.txt replaces the file with notes.txt.gz. gzip -k notes.txt keeps the original too. gunzip notes.txt.gz (or gzip -d) turns it back. zcat notes.txt.gz prints the content without unpacking anything, which is how you read old rotated logs like syslog.2.gz. zgrep searches inside them the same way.

Walking through the code. The # Setup: lines only create the sample files, so skip past them.

  1. tar -czf logs.tar.gz logs packs the folder.
  2. tar -tzf logs.tar.gz lists the contents, and logs/ with a slash is the folder itself. tar stores files in whatever order the disk gives them, so sort makes the list stable.
  3. mkdir restore comes first, because -C needs the folder to exist. tar -xzf ... -C restore unpacks there, so the files land in restore/logs/. find restore -type f | sort lists the files.
  4. gzip -k notes.txt makes notes.txt.gz and keeps notes.txt. ls -1 notes.txt* shows both, and zcat reads the compressed one.

Always list before you unpack. tar -xzf unpacks into the current folder and overwrites any files with the same names, without asking. Running tar -tzf first shows you what will appear and where. This is extra important for archives from someone else.

Edge cases. An empty folder still packs fine, and the archive holds just the folder entry. If the archive name is wrong, tar stops with "Cannot open: No such file or directory". gzip refuses to compress a file that already ends in .gz.

# Setup: a logs folder with two files, plus a notes file
cd "$(mktemp -d)"
mkdir logs
printf 'GET /home 200\nGET /cart 200\n' > logs/access.log
printf 'db timeout\n' > logs/error.log
printf 'deploy went fine\n' > notes.txt

# 1. Pack: c = create, z = gzip it, f = the archive file name comes next
tar -czf logs.tar.gz logs

# 2. Look inside without unpacking: t = list
echo "== inside logs.tar.gz =="
tar -tzf logs.tar.gz | sort

# 3. Unpack into another folder: x = extract, -C = go to this folder first
mkdir restore
tar -xzf logs.tar.gz -C restore
echo "== unpacked files =="
find restore -type f | sort
echo "== restore/logs/error.log =="
cat restore/logs/error.log

# 4. gzip one file; -k keeps the original next to the .gz
gzip -k notes.txt
echo "== notes files =="
ls -1 notes.txt*
echo "== read the .gz without unpacking =="
zcat notes.txt.gz

Interview follow-ups

  • How do you archive only the log files that changed in the last day?

    Let find choose the files and hand the list to tar. find logs -name '*.log' -mtime -1 -print0 | tar --null -czf recent.tar.gz -T - does it. -mtime -1 means "changed less than a day ago". -print0 ends each name with a special null character, and --null -T - tells tar to read that list from the pipe. Using null characters keeps names with spaces safe. These flags are GNU tar, which is the tar on almost every Linux server.

Frequently asked questions

You packed an absolute path, like tar -czf etc.tar.gz /etc. tar strips the leading /, so the archive holds etc/... instead of /etc/.... This is a safety feature: when you unpack it later, the files go into the current folder instead of overwriting the real /etc. It is a notice, not an error. To avoid it, change into the parent folder first: tar -czf etc.tar.gz -C / etc.

On Linux servers, .tar.gz is the usual choice. tar and gzip are installed almost everywhere, and tar keeps Linux permissions and owners, which matters for scripts and configs. .zip is better when the archive is going to Windows users, because Windows opens it without extra tools. Other formats exist too: .tar.bz2 (j flag) and .tar.xz (J flag) shrink more but are slower. Use file name.tar.gz if you are not sure what an archive really is.

Give tar the path of that file as it appears in the listing: tar -xzf logs.tar.gz logs/error.log. List the archive first with tar -tzf, so you copy the exact path, including the folder at the front. To print it without writing anything to disk, add -O: tar -xzOf logs.tar.gz logs/error.log. This saves time and disk space with large backups.