Skip to content

Instantly share code, notes, and snippets.

@nyteshade
Created June 18, 2026 18:40
Show Gist options
  • Select an option

  • Save nyteshade/7c9f9cde7daf23c840a156e83abb7a73 to your computer and use it in GitHub Desktop.

Select an option

Save nyteshade/7c9f9cde7daf23c840a156e83abb7a73 to your computer and use it in GitHub Desktop.
unpkg
#!/bin/zsh
setopt pipefail
showUsage() {
local tool="\x1b[1;35munpkg\x1b[22;39m"
printf "${tool} <path-to-package.pkg> <destination-directory>\n"
printf "${tool} --to-zip <path-to-package.pkg> [destination.zip]\n"
printf "${tool} -z <path-to-package.pkg> [destination.zip]\n"
printf "${tool} --to-tar-gzip <path-to-package.pkg> [destination.tar.gz]\n"
printf "${tool} -tgz <path-to-package.pkg> [destination.tar.gz]\n\n"
printf "${tool} extracts macOS .pkg archives into contents/ and scripts/.\n"
printf "Payload and Scripts archives may be gzip-compressed cpio or plain cpio.\n"
}
missingFile() {
printf "The file specified ${1}, cannot be found.\n"
}
requireCommand() {
if ! command -v "${1}" >/dev/null 2>&1; then
printf "Required command '${1}' was not found.\n"
cleanup 1
fi
}
cleanup() {
local exitStatus="${1:-$?}"
trap - EXIT INT TERM
cd "${curDir}" 2>/dev/null
if [[ -n "${tmpDir}" && -d "${tmpDir}" ]]; then
rm -rf "${tmpDir}"
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not remove temp directory '${tmpDir}'\n"
exit 1
fi
fi
exit "${exitStatus}"
}
extractCpioArchive() {
local archive="${1}"
local outDir="${2}"
local label="${3:-archive}"
mkdir -p "${outDir}"
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not create output directory ${outDir}\n"
cleanup 1
fi
cd "${outDir}"
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not enter output directory ${outDir}\n"
cleanup 1
fi
if gzip -t "${archive}" >/dev/null 2>&1; then
gzip -dc "${archive}" | cpio -idm
else
cpio -idm < "${archive}"
fi
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not extract ${label} archive ${archive}\n"
cleanup 1
fi
}
installRootForPayload() {
local payload="${1}"
local contentsRoot="${2}"
local packageInfo="$(dirname "${payload}")/PackageInfo"
local installLocation="/"
local relativeLocation=""
if [[ -f "${packageInfo}" ]]; then
installLocation="$(sed -n 's/.*install-location="\([^"]*\)".*/\1/p' "${packageInfo}" | head -n 1)"
fi
if [[ -z "${installLocation}" || "${installLocation}" == "/" ]]; then
printf "%s" "${contentsRoot}"
return
fi
relativeLocation="${installLocation#/}"
if [[ "${relativeLocation}" == *".."* ]]; then
printf "Refusing unsafe install-location '${installLocation}' in ${packageInfo}\n" >&2
cleanup 1
fi
printf "%s/%s" "${contentsRoot}" "${relativeLocation}"
}
makeZip() {
local sourceDir="${1}"
local zipFile="${2}"
local zipParent="$(dirname "${zipFile}")"
local zipName="$(basename "${zipFile}")"
mkdir -p "${zipParent}"
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not create zip output directory ${zipParent}\n"
cleanup 1
fi
zipFile="$(cd "${zipParent}" && pwd)/${zipName}"
rm -f "${zipFile}"
cd "${sourceDir}"
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not enter staging directory ${sourceDir}\n"
cleanup 1
fi
zip -qry "${zipFile}" .
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not create zip file ${zipFile}\n"
cleanup 1
fi
}
makeTarGzip() {
local sourceDir="${1}"
local tarFile="${2}"
local tarParent="$(dirname "${tarFile}")"
local tarName="$(basename "${tarFile}")"
mkdir -p "${tarParent}"
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not create tar output directory ${tarParent}\n"
cleanup 1
fi
tarFile="$(cd "${tarParent}" && pwd)/${tarName}"
rm -f "${tarFile}"
cd "${sourceDir}"
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not enter staging directory ${sourceDir}\n"
cleanup 1
fi
tar -czf "${tarFile}" .
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not create tar gzip file ${tarFile}\n"
cleanup 1
fi
}
defaultArchiveDestination() {
local sourceFile="${1}"
local extension="${2}"
local sourceDir="$(dirname "${sourceFile}")"
local sourceName="$(basename "${sourceFile}")"
local baseName="${sourceName%.*}"
printf "%s/%s%s" "${sourceDir}" "${baseName}" "${extension}"
}
ensureArchiveExtension() {
local outputFile="${1}"
local extension="${2}"
local alternateExtension="${3}"
local lowerOutput="${(L)outputFile}"
if [[ "${lowerOutput}" == *"${extension}" || ( -n "${alternateExtension}" && "${lowerOutput}" == *"${alternateExtension}" ) ]]; then
printf "%s" "${outputFile}"
else
printf "%s%s" "${outputFile}" "${extension}"
fi
}
local archiveMode=""
if [[ "${1}" == "--to-zip" || "${1}" == "-z" ]]; then
archiveMode="zip"
shift
elif [[ "${1}" == "--to-tar-gzip" || "${1}" == "-tgz" ]]; then
archiveMode="tgz"
shift
fi
if [[ -n "${archiveMode}" && ( "${#}" -lt 1 || "${#}" -gt 2 ) ]]; then
showUsage
exit 0
fi
if [[ -z "${archiveMode}" && "${#}" -ne 2 ]]; then
showUsage
exit 0
fi
if ! [[ -f "${1}" ]]; then
missingFile "${1}"
exit 1
fi
requireCommand xar
requireCommand gzip
requireCommand cpio
if [[ "${archiveMode}" == "zip" ]]; then
requireCommand zip
elif [[ "${archiveMode}" == "tgz" ]]; then
requireCommand tar
fi
local file="$(cd "$(dirname "${1}")" && pwd)/$(basename "${1}")"
local curDir="$(pwd)"
local destination="${2}"
local tmpDir="$(mktemp -d /tmp/unpkg.XXXXXXXX)"
local pkgDir="${tmpDir}/pkg"
local extractDir="${destination}"
local contentsDir="${destination}/contents"
local scriptsDir="${destination}/scripts"
if [[ "${archiveMode}" == "zip" ]]; then
if [[ -z "${destination}" ]]; then
destination="$(defaultArchiveDestination "${file}" ".zip")"
else
destination="$(ensureArchiveExtension "${destination}" ".zip")"
fi
elif [[ "${archiveMode}" == "tgz" ]]; then
if [[ -z "${destination}" ]]; then
destination="$(defaultArchiveDestination "${file}" ".tar.gz")"
else
destination="$(ensureArchiveExtension "${destination}" ".tar.gz" ".tgz")"
fi
fi
if [[ "${destination}" != /* ]]; then
destination="${curDir}/${destination}"
fi
extractDir="${destination}"
contentsDir="${destination}/contents"
scriptsDir="${destination}/scripts"
if ! [[ -d "${tmpDir}" ]]; then
printf "Could not create temporary directory.\n"
exit 1
fi
trap 'cleanup $?' EXIT INT TERM
mkdir -p "${pkgDir}"
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not create temporary package directory ${pkgDir}\n"
cleanup 1
fi
cd "${pkgDir}"
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not enter temporary package directory ${pkgDir}\n"
cleanup 1
fi
xar -xf "${file}"
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not extract package ${file}\n"
cleanup 1
fi
local -a payloads rawPayloads
rawPayloads=( "${(@f)$(find "${pkgDir}" -type f -name 'Payload' -print)}" )
payloads=()
for payloadPath in "${rawPayloads[@]}"; do
if [[ -n "${payloadPath}" ]]; then
payloads+=( "${payloadPath}" )
fi
done
local -a scripts rawScripts
rawScripts=( "${(@f)$(find "${pkgDir}" -type f -name 'Scripts' -print)}" )
scripts=()
for scriptPath in "${rawScripts[@]}"; do
if [[ -n "${scriptPath}" ]]; then
scripts+=( "${scriptPath}" )
fi
done
if [[ "${#payloads}" -eq 0 ]]; then
printf "Could not find a file called 'Payload' in the extracted contents.\n"
printf "Find the file, move to your target directory, then run one of:\n"
printf "gzip -dc ${tmpDir}/path/to/Payload | cpio -idm\n"
printf "cpio -idm < ${tmpDir}/path/to/Payload\n"
cleanup 1
fi
if [[ -n "${archiveMode}" ]]; then
extractDir="${tmpDir}/contents"
contentsDir="${extractDir}/contents"
scriptsDir="${extractDir}/scripts"
fi
mkdir -p "${contentsDir}" "${scriptsDir}"
if ! [[ "${?}" -eq 0 ]]; then
printf "Could not create output directories under ${extractDir}\n"
cleanup 1
fi
for payload in "${payloads[@]}"; do
extractCpioArchive "${payload}" "$(installRootForPayload "${payload}" "${contentsDir}")" "Payload"
done
for scriptArchive in "${scripts[@]}"; do
extractCpioArchive "${scriptArchive}" "${scriptsDir}" "Scripts"
done
if [[ "${archiveMode}" == "zip" ]]; then
makeZip "${extractDir}" "${destination}"
elif [[ "${archiveMode}" == "tgz" ]]; then
makeTarGzip "${extractDir}" "${destination}"
fi
cleanup 0
@nyteshade

Copy link
Copy Markdown
Author

I generally rename this to unpkg and place it in my ~/.local/bin, but this has helped me get the files and scripts out of an Apple .pkg file without executing it. This can be important in constrained environments.

It's tested on my local needs thus far, your mileage may vary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment