This is archive of various scripts for bash i've made to automate something or as proof-of-concepts. Somewhen they gonna be documented, but probably not. The name was chosen because of its meaning – place where manuscripts were manually copied called «scriptorium», and as I decided just to insert scripts in page code, you have to copy it manually as well. Appearance and layout of this page slightly differs from rest of site, but I think it's justified decision for this contents.
#!/bin/bash
ARCHIVE_INDEX="index.catpak.lst"
ARCHIVE_CONTENT="index.catpak"
function create_packageset {
    truncate -s 0 ./$ARCHIVE_INDEX
    truncate -s 0 ./$ARCHIVE_CONTENT
    find -type f ! -iname "$ARCHIVE_INDEX" ! -iname "$ARCHIVE_CONTENT" -exec sh -c 'stat -c "%s^%n^%a" "$1" >> "./$2"; cat "$1" >> "./$3"' _ {} "$ARCHIVE_INDEX" "$ARCHIVE_CONTENT" \;
}
function extract_file {
    CHUNK=$(awk -F '^' -v needle="$1" 'BEGIN { sum = 0 } $2 == needle { print sum " " $1; exit 1 } $2 != needle { sum += $1 }' "./$ARCHIVE_INDEX")
    if [[ "$?" != "1" ]]
    then
        exit 1
    fi
    IFS=" " read -ra OFFLIM <<< "$CHUNK"
    dd if="./$ARCHIVE_CONTENT" of="/tmp/dumpdata" bs=1 skip="${OFFLIM[0]}" count="${OFFLIM[1]}" && \
    cat /tmp/dumpdata
    rm /tmp/dumpdata
}
function extract_all {
    OFFSET=0
    mkdir -p "$1"
    for LISTENTRY in $(cat "./$ARCHIVE_INDEX")
    do
        ITEMSIZE=$(cut -d^ -f1 <<< "$LISTENTRY")
        ITEMNAME="$1/$(cut -d^ -f2 <<< $LISTENTRY)"
        mkdir -p $(dirname $ITEMNAME)
        dd if="./$ARCHIVE_CONTENT" of="$ITEMNAME" bs=1 skip="$OFFSET" count="$ITEMSIZE"
        OFFSET=$(expr $OFFSET + $ITEMSIZE)
    done
}
if [[ "$1" == "create" ]]
then
    create_packageset
elif [[ "$1" == "extract" ]]
then
    extract_file "./$2"
elif [[ "$1" == "extractall" ]]
then
    extract_all "/tmp/dumpie"
elif [[ "$1" == "squash" ]]
then
    create_packageset
    # tar czvf index.catpak.tgz "./$ARCHIVE_INDEX" "./$ARCHIVE_CONTENT"
    # rm "./$ARCHIVE_INDEX" "./$ARCHIVE_CONTENT"
    bzip2 "./$ARCHIVE_CONTENT"
elif [[ "$1" == "unsquash" ]]
then
    bzip2 -d -c "./$ARCHIVE_CONTENT.bz2" > "./$ARCHIVE_CONTENT"
    # tar xzvf index.catpak.tgz
    extract_file "./$2"
    # rm "./$ARCHIVE_INDEX" "./$ARCHIVE_CONTENT"
    rm "./$ARCHIVE_CONTENT"
fi
#!/usr/bin/env bash
if [ "$1" == "make" ]
then
    if [ -f rootCA.key ]
    then
    echo "CA key and certificate exist"
    exit 1
    fi
    # ca key
    openssl genrsa -out rootCA.key 2048
    # ca cert
    openssl req -x509 -new -key rootCA.key -days 10000 -out rootCA.crt
    exit 0
fi
if [ "$1" == "sign" ] && [ "$2" != "" ]
then
    openssl genrsa -out $2.key 2048
    openssl req -new -key $2.key -out $2.csr
    openssl x509 -req -in $2.csr -CA rootCA.crt -CAkey rootCA.key -CAcreateserial -out $2.crt -days 5000
    exit 0
fi
echo "Usage: $0 make|sign <result basename>"
exit 1
#!/bin/sh
# topdu -- du -h for top-level directoies
du . | \
sed -rn '/^[0123456789]+[[:space:]]+\.\/*[^\/]*$/p' | \
sort -n | \
awk 'BEGIN { FS = "\t" } {
    dsize = $1 "K";
    if($1 > lshift(1, 20)) {
        dsize = rshift($1, 20) "G"
    } else if($1 > lshift(1, 10)) {
        dsize = rshift($1, 10) "M"
    };
    print dsize "\t" $2
}'
#!/bin/bash
# view gemini capsules
gemini_host=$(printf "$1" |  sed 's#^gemini://\([^/]*\).*$#\1#gi')
if [ "$2" != "" ]
then
    gemini_path="$2"
else
    gemini_path="$1"
fi
printf "$gemini_path\r\n" | openssl s_client -quiet -ign_eof -connect $gemini_host:1965 2> /dev/null 1> /dev/shm/gemini.tmp
read gemini_status gemini_meta <<< $(head -n 1 /dev/shm/gemini.tmp | tr -d '\r\n')
printf "Status: %s, Meta: %s\n" $gemini_status $gemini_meta
clear
case "$gemini_status" in
    2*)
        if [ "$gemini_meta" == "text/gemini" ]; then
            tail -n +2 /dev/shm/gemini.tmp | sed 's/^\(=>.*$\)/\o033[1;36m\1\o033[0m/gi'
        else
            tail -n +2 /dev/shm/gemini.tmp
        fi
        ;;
    3*)
        printf "Redirecting to $gemini_meta...\n"
        $0 "$gemini_meta"
        ;;
    4*|5*)
        printf "Error: $gemini_meta!\n"
        ;;
esac
if [ -e "/dev/shm/gemini.tmp" ]; then rm /dev/shm/gemini.tmp; fi
Handles basic cases only
#!/bin/bash
if [ "$1" == "" ]
then
    find . -iname '*.htm*' -exec "$0" {} \;
else
    printf "\n$1 dependencies:\n\n"
    cat "$1" | sed 's/\n[:space:]]+).*/\2/gi;p}' | tr -d '"'"'"''
fi
#!/bin/bash
PATSH_REPO=".patches"
PATSH_NAME="patsh"
function check_if_unchanged {
    unset -v LATEST_DIFF
    for file in "${PATSH_REPO}/${1}"-*.diff; do
        [[ "$file" -nt "${LATEST_DIFF}" ]] && LATEST_DIFF="$file"
    done
    if [ "$LATEST_DIFF" != "" ]; then
        patch -i "${LATEST_DIFF}" -o - "${PATSH_REPO}/${1}" 2> /dev/null | cmp - "${1}" > /dev/null
    else
        cmp "${PATSH_REPO}/${1}" "${1}" > /dev/null
    fi
}
function showhelp {
    cat <<EOF
${PATSH_NAME} -- dead simple patch/version control system
Usage:
    patsh [options] command [files...]
Options:
  -h        show this help
  -f        enforce commit if no changes
Commands:
  init      initialize stash area
  add       start tracking file changes
  save      store changes done to file
  restore   undo changes and rewind to last version
  diff      show changes between working copy and last version
  list      show stored versions
EOF
    exit 0
}
while [[ "$1" == -* ]]; do
    case "$1" in
        -f|--force)
            printf "${PATSH_NAME}: not implemented yet\n"
            ;;
        -h|--help)
            showhelp
            ;;
    esac
    shift
done
case "$1" in
    init)
        mkdir "${PATSH_REPO}" && \
        printf "${PATSH_NAME}: stash initialized\n"
        ;;
    add|a)
        while [ -e "$2" ]; do
        if [ ! -e "${PATSH_REPO}/$2" ]; then
            cp "$2" "${PATSH_REPO}/" && \
            printf "${PATSH_NAME}: tracked file: ${2}\n"
        else
            printf "${PATSH_NAME}: file already tracked\npat.sh: use ${0} save ${2} to commit changes\n"
        fi
        shift
        done
        ;;
    list|ls)
        while [ -e "$2" ]; do
        if [ ! -e "${PATSH_REPO}/$2" ]; then
            printf "${PATSH_NAME}: file not tracked\npat.sh: use ${0} add ${2} to track file\n"
            exit 0
        fi
        printf "${PATSH_NAME}: ${2}: saved patches\n"
        for file in "${PATSH_REPO}/${2}"-*.diff ; do
            basename "${file}"
        done | sort
        shift
        done
        ;;
    save|ci)
        while [ -e "$2" ]; do
        if [ ! -e "${PATSH_REPO}/$2" ]; then
            printf "${PATSH_NAME}: file ${2} not tracked\n"
        elif check_if_unchanged "$2"; then
            printf "${PATSH_NAME}: file ${2} not changed\n"
        else
            LATEST_DIFF="${PATSH_REPO}/$2-$(date +%s).diff"
            diff -u "${PATSH_REPO}/$2" "$2" > "${LATEST_DIFF}"
            printf "${PATSH_NAME}: changes written to %s\n" "${LATEST_DIFF}"
        fi
        shift
        done
        ;;
    restore|co)
        while [ -e "$2" ]; do
        unset -v LATEST_DIFF
        if [ ! -e "${PATSH_REPO}/$2" ]; then
            printf "${PATSH_NAME}: file ${2} not tracked\n"
            exit 0
        fi
        cp "${PATSH_REPO}/${2}" "${2}"
        for file in "${PATSH_REPO}/${2}"-*.diff; do
            [[ "$file" -nt "${LATEST_DIFF}" ]] && LATEST_DIFF="$file"
        done
        if [ "$LATEST_DIFF" != "" ]; then
            patch "${2}" < "${LATEST_DIFF}" > /dev/null
            printf "${PATSH_NAME}: reset to ${LATEST_DIFF}\n"
        else
            printf "${PATSH_NAME}: reset to original version\n"
        fi
        shift
        done
        ;;
    diff)
        while [ -e "$2" ]; do
        unset -v LATEST_DIFF
        printf "${PATSH_NAME}: ${2} working copy changes\n"
        for file in "${PATSH_REPO}/${2}"-*.diff; do
            [[ "$file" -nt "${LATEST_DIFF}" ]] && LATEST_DIFF="$file"
        done
        if [ "$LATEST_DIFF" != "" ]; then
            patch -i "${LATEST_DIFF}" -o - "${PATSH_REPO}/${2}" 2> /dev/null | diff --color=always - "${2}"
        else
            diff --color=always "${PATSH_REPO}/${2}" "${2}"
        fi
        shift
        done
        ;;
    *)
        if [ "$1" == "" ]; then showhelp; fi
        printf "${PATSH_NAME}: unknown operation: ${1}\n"
        ;;
esac
#!/usr/bin/python3
import enum, socket, traceback, subprocess
class MenuType(str, enum.Enum):
    DOC = '0'
    MENU = '1'
    QUERY = '7'
    BIN = '9'
    HTML = 'h'
    INFO = 'i'
    SOUND = 's'
    IMAGE = 'I'
    GIF = 'g'
ESC_WHITE = '\033[1;37m'
ESC_CYAN  = '\033[1;36m'
ESC_RESET = '\033[0m'
ESC_RED   = '\033[1;31m'
ESC_GREEN = '\033[1;32m'
ESC_YELLO = '\033[1;33m'
ESC_BLUE  = '\033[1;34m'
class Fetcher(object):
    def __init__(self):
        self.mode = MenuType.MENU
        self.host = 'gopher.floodgap.com'
        self.port = 70
        self.path = ''
        self.history = []
    def go(self, mode=None, host=None, port=None, path=None):
        self.history.append((
            self.mode, self.host, self.port, self.path
        ))
        if mode is not None:
            self.mode = mode
        if host is not None:
            self.host = host
        if port is not None:
            self.port = int(port)
        if path is not None:
            self.path = path
        if mode == MenuType.QUERY:
            query = input('Search query: ')
            self.path += '\t{}'.format(query)
    def back(self):
        try:
            mode, host, port, path = self.history.pop()
            self.mode = mode
            self.host = host
            self.port = port
            self.path = path
        except:
            print('No entries in history')
    def fetch(self):
        try:
            s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            s.connect((self.host, self.port))
            s.sendall('{}\r\n'.format(self.path).encode('utf-8'))
            resp = b''
            while True:
                chunk = s.recv(1024)
                if not chunk:
                    break
                resp += chunk
            return resp
        except:
            traceback_exc()
            print('Failed to fetch data')
            return None
class Navigator(Fetcher):
    def __init__(self):
        super().__init__()
        self.links = []
    def goto(self, num):
        if num <= 0 or num > len(self.links):
            print('\tWrong link number')
        else:
            self.go(*self.links[num - 1])
    def display(self):
        if self.mode == MenuType.BIN:
            print('Enter filename or leave empty to ignore')
            filename = input('Save as: ')
            if filename == '':
                return
        elif self.mode == MenuType.HTML:
            if self.path.lower().startswith('url:'):
                subprocess.call(['/usr/bin/xdg-open', self.path[4:]])
            else:
                subprocess.call(['/usr/bin/lynx', 'gopher://{}:{}/0{}'.format(self.host, self.port, self.path)])
            self.back()
            return
        data = self.fetch()
        if self.mode == MenuType.DOC:
            print(data.decode('utf-8'))
        elif self.mode == MenuType.BIN:
            with open(filename, 'wb') as f:
                f.write(data)
        elif self.mode == MenuType.MENU:
            self.links = []
            items = [item.split('\t') for item in data.decode('utf-8').split('\n')]
            for item in items:
                if len(item) < 4:
                    continue
                itype = item[0][0]
                itext = item[0][1:]
                if itype == MenuType.INFO:
                    print('\t{}'.format(itext))
                elif itype == MenuType.DOC:
                    self.links.append((itype, item[2], item[3], item[1]))
                    print('{}[{}]\t{}{}{}'.format(
                        ESC_WHITE,
                        len(self.links),
                        ESC_BLUE,
                        itext,
                        ESC_RESET
                    ))
                elif itype == MenuType.MENU:
                    self.links.append((itype, item[2], item[3], item[1]))
                    print('{}[{}]\t{}{}{}'.format(
                        ESC_WHITE,
                        len(self.links),
                        ESC_WHITE,
                        itext,
                        ESC_RESET
                    ))
                elif itype == MenuType.BIN or itype == MenuType.SOUND or itype == MenuType.IMAGE or itype == MenuType.GIF:
                    self.links.append((itype, item[2], item[3], item[1]))
                    print('{}[{}]\t{}{}{}'.format(
                        ESC_WHITE,
                        len(self.links),
                        ESC_GREEN,
                        itext,
                        ESC_RESET
                    ))
                elif itype == MenuType.QUERY:
                    self.links.append((itype, item[2], item[3], item[1]))
                    print('{}[{}]\t{}{}{}'.format(
                        ESC_WHITE,
                        len(self.links),
                        ESC_CYAN,
                        itext,
                        ESC_RESET
                    ))
                elif itype == MenuType.HTML:
                    self.links.append((itype, item[2], item[3], item[1]))
                    print('{}[{}]\t{}{}{}'.format(
                        ESC_WHITE,
                        len(self.links),
                        ESC_RED,
                        itext,
                        ESC_RESET
                    ))
                else:
                    print('\tUnknown type: {}'.format(itype))
        else:
            print('Not yet implemented')
def display_help():
    print('''Omsk subway -- commandline gopher navigator
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Commands:
exit       -- exit program
help       -- view this help
back       -- return to previous menu
index      -- return to server index
@<host>    -- request server's index
<selector> -- request menu by selector on current
              server
''')
if __name__ == '__main__':
    navi = Navigator()
    omit_display = False
    while True:
        try:
            if not omit_display:
                navi.display()
            else:
                omit_display = False
        except:
            traceback.print_exc()
            print('\tFailed to render')
        command = input('{}{} {}{}> {}'.format(ESC_WHITE, navi.host, ESC_CYAN, navi.path, ESC_RESET))
        if command == '':
            continue
        elif command == 'help' or command == '?':
            display_help()
            omit_display = True
        elif command == 'exit':
            break
        elif command == 'back':
            navi.back()
        elif command == 'index':
            navi.go(path='')
        elif command[0] == '@':
            navi.go(host=command[1:], path='', mode=MenuType.MENU)
        elif command.isdigit():
            navi.goto(int(command))
        else:
            navi.go(path=command, mode=MenuType.MENU)
Code provided under CC-BY-SA license