Skip to content

Instantly share code, notes, and snippets.

@jow-
Created February 23, 2024 09:04
Show Gist options
  • Save jow-/3cfbdb19342c2ebcdeaa4a4daef42433 to your computer and use it in GitHub Desktop.
Save jow-/3cfbdb19342c2ebcdeaa4a4daef42433 to your computer and use it in GitHub Desktop.
Generate a .tar.gz archive with shell
#!/bin/sh
make_tar_member() {
local name=$1
local content=$2
local mode=644
local uid=1000
local gid=1000
local size=${#content}
local mtime=$(date +%s)
local type=0
local link=""
local username="jow"
local groupname="jow"
# 100 padding bytes, using 0x01 since the shell does not tolerate null bytes in strings
local pad=$'\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1'
# truncate string header values to their maximum length
name=${name:0:100}
link=${link:0:100}
username=${username:0:32}
groupname=${groupname:0:32}
# construct header part before checksum field
local header1="${name}${pad:0:$((100 - ${#name}))}"
header1="${header1}$(printf '%07d\1' $mode)"
header1="${header1}$(printf '%07o\1' $uid)"
header1="${header1}$(printf '%07o\1' $gid)"
header1="${header1}$(printf '%011o\1' $size)"
header1="${header1}$(printf '%011o\1' $mtime)"
# construct header part after checksum field
local header2="$(printf '%d' $type)"
header2="${header2}${link}${pad:0:$((100 - ${#link}))}"
header2="${header2}ustar ${pad:0:1}"
header2="${header2}${username}${pad:0:$((32 - ${#username}))}"
header2="${header2}${groupname}${pad:0:$((32 - ${#groupname}))}"
# calculate checksum over header fields
local checksum=0
for byte in $(printf '%s%8s%s' "$header1" "" "$header2" | tr '\1' '\0' | hexdump -ve '1/1 "%u "'); do
checksum=$((checksum + byte))
done
# print member header, padded to 512 byte
printf '%s%06o\0 %s' "$header1" $checksum "$header2" | tr '\1' '\0'
dd if=/dev/zero bs=183 count=1 2>/dev/null
# print content data, padded to multiple of 512 byte
printf "%s" "$content"
dd if=/dev/zero bs=1 count=$((512 - (size % 512))) 2>/dev/null
}
{
make_tar_member "example.txt" "Hello there"
make_tar_member "example-2.txt" "Blah blah"
# alternatively, instead of dd'ing the trailing padding, run a `tar -c ...` here
dd if=/dev/zero bs=1024 count=1 2>/dev/null
} | gzip > /tmp/test.tar.gz
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment