#!/bin/sh # lshar.sh - Luxferre's shell archiver # Outputs to stdout only (for GNU and BSD shar compatibility) # No external deps for .shars (sh commands required: mkdir, printf) # Archive creation also depends on sort (Busybox, Toybox or GNU Coreutils) # Works with any text or binary files and directory structures # Created by Luxferre in 2023, released into public domain # a portable, compact, no-deps serializer of any file into printf sequence f2p() { c=0 # init counter l="$2" # get chunk length [ -z "$l" ] && l=4096 # 4096-byte chunks by default (shouldn't be a problem) cprefix="printf '%b' '" # pattern to prepend to every chunk csuffix="' >> '$1'\n" # pattern to append to every chunk printf '%b' "$cprefix" # always start with the prefix IFS='' # separator while read -r -s -d '' -n1 b; do # convert every byte if [ "$c" == "$l" ]; then # roll over and output suffix+prefix c=0 # reinit counter printf '%b' "$csuffix" # end the chunk printf '%b' "$cprefix" # begin a new chunk else c="$(( c + 1 ))" # increment c fi bcode="$(printf '%d' "'$b")" # get the ASCII code of the byte if [ "$bcode" -gt 32 ] && [ "$bcode" -lt 127 ] && [ "$bcode" -ne 39 ] && [ "$bcode" -ne 92 ] then # printable and not a single quote or a backslash printf '%b' "$b" # codes from 34 to 126 are safe to print else # subject to escaping, print the hex representation printf '\\x%02x' "$bcode" fi done <"$1" printf '%b' "$csuffix" # always end with the suffix } # main code LF=$'\n' # cache the linefeed character flist='' # pre-build the file list from args dlist='' # pre-build the directory list from args for i; do # iterate over args bname="${i##*/}" # get the basename if [ -d "$i" ]; then # it's a directory, we skip . and .. [ "$bname" == '.' ] || [ "$bname" == '..' ] && continue dlist="${dlist}${i%/}${LF}" # append the directory else # it's a file dname="${i%%$bname}" # get the basedir dlist="${dlist}${dname%/}${LF}" # append the basedir flist="${flist}${i}${LF}" # append the file fi done flist="$(printf '%s' "$flist" | sort -u)" # remove file duplicates dlist="$(printf '%s' "$dlist" | sort -u)" # remove directory duplicates printf '%s\n' '#!/bin/sh' # output the shebang, just in case IFS="$LF" # update the separator for indir in $dlist; do # create the directory structure first [ -z "$indir" ] && continue # skip empty names printf '# dir: %s\n' "$indir" printf "printf 'c - %%s\\\n' '%s'\n" "$indir" # announce creation printf "mkdir -p '%s' > /dev/null 2>&1\n" "$indir" # create the dir done for infile in $flist; do # iterate over the file list afterwards [ -z "$infile" ] && continue # skip empty names printf '# file: %s\n' "$infile" printf "printf 'x - %%s\\\n' '%s'\n" "$infile" # announce unrolling printf "printf '' > '%s'\n" "$infile" # clear the file f2p "$infile" # run the serializer IFS="$LF" # update the separator done