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