Shorthands for Common Tasks
bput
#!/bin/bash
NAME="$1"
if [ -z "$NAME" ]; then
echo "bput <name>"
exit 1
fi
shift
cat "$@" | bclient put "$NAME"
bget
#!/bin/bash
for s; do
bclient get "$s"
done
blist
#!/bin/bash
if [ -n "$1" ]; then
bclient list | grep "$@"
else
bclient list
fi
Link with clipboard
I have the commands pc and pp (shorted from macos' pbcopy and pbpaste, but do the
right thing on Linux, Windows, and mac).
bpc
#!/bin/bash
# bget into clipboard
for s; do
bclient get "$s"
done | pc
bpp
#!/bin/bash
NAME="$1"
shift
if [ -z "$NAME" ]; then
echo "bpp <name>"
exit 1
fi
pp | bclient put "$NAME"
Signalling Task Completion
Something I sometimes want to do is to start a task on one machine, and then have another machine wait until it is done before doing something else. A simple solution is to create a named clip for the task, and then delete it when done.
bwait
#!/bin/bash
X="$1"
if [ -z "$X" ]; then
echo "$0 <name>"
exit 1
fi
while [ -n "$(bget "$X")" ]; do
echo -n "Waiting until $X is free: "; date
sleep 1
done
bwrap
#!/bin/bash
X="$1"; shift
CMD="$1"; shift
if [ -z "$X" ] || [ -z "$CMD" ]; then
echo "$0 <name> <cmd> [<args>]"
exit 1
fi
while [ -n "$(agt "$X")" ]; do
echo -n "Waiting until $X is free: "; date
sleep 1
done
echo "Starting $X"
echo $$ | bput "$X"
"$CMD" "$@"
bclient del "$X"
The Client
bclient
#!/usr/bin/env python3
import sys
import subprocess
import socket
import os
import re
from datetime import datetime
# specifying . as source file pastes from clipoard
HOST = os.getenv("BHOST","127.0.0.1") # The server's hostname or IP address
PORT = os.getenv("BPORT",9999) # The port used by the server
try:
PORT = int(PORT)
except ValueError:
print(f"Invalid port PORT={PORT}")
PORT=None
def paste():
m = subprocess.run(["pp"],capture_output=True)
f = sys.stderr
if (x := m.returncode) > 0:
out = m.stdout.splitlines()
err = m.stderr.splitlines()
print(f"pp returned {x}:",file=f)
print(f"stdout:",file=f)
for line in out:
line = b" " + line + b"\n"
f.buffer.write(line)
print(f"stderr:",file=f)
for line in err:
line = b" " + line + b"\n"
f.buffer.write(line)
return ""
else:
return m.stdout.decode()
def helpexit(n):
print(f"{sys.argv[0]} <cmd> <name>")
print(f" where <cmd> is one of:")
for k in cmds.keys():
v = cmds[k]
if type(v) == Command:
print(f" {k} -- {v.helptext}")
else:
print(f" {k}")
print(f"When putting or appending, send content to stdin")
exit(n)
def main():
global PORT, HOST
append = False
args = sys.argv[1:]
argi = iter(args)
args = []
for arg in argi:
if arg == "-h":
try:
HOST = next(argi)
except StopIteration:
print(f"-h needs argument",file=sys.stderr)
exit(1)
elif arg == "-a":
append = True
else:
args.append(arg)
try:
cmd, *params = args
except ValueError:
helpexit(1 if len(args) > 0 else 0)
if len(args) > 0:
exit(1)
else:
exit(0)
if cmd == "put" and append:
cmd = "append"
if cmd in cmds:
try:
cmds[cmd](params)
except BException as e:
print(f"BException: {e} running {cmd}({', '.join(params)})")
else:
print(f"No command {cmd}")
helpexit(1)
class BException(Exception):
pass
def do_get(params):
if len(params) == 0:
raise BException("No names to get")
for name in params:
msg = f"get {name}"
print(query(msg),end="")
def do_save(params):
try:
name, = params
except ValueError:
raise BException("Invalid params for save")
msg = f"save {name}"
print(query(msg),end="")
def do_load(params):
try:
name, = params
except ValueError:
raise BException("Invalid params for load")
msg = f"load {name}"
print(query(msg))
def do_list(params):
msg = f"list"
result = query(msg)
if len(params) > 0:
matches = set()
lines = result.strip().splitlines()
for x in params:
r = re.compile(x)
for y in lines:
z = y.split(":")[0]
if r.search(z):
matches.add(y)
result = "\n".join(sorted(matches))
print(result)
def do_listsaved(params):
msg = f"listsaved"
result = query(msg)
lines = result.rstrip().splitlines()
namel = 0
out = []
for line in lines:
xs = line.split(",")
mtime = xs.pop()
name = ",".join(xs)
namel = max(namel,len(name))
mtime = float(mtime)
mdate = datetime.fromtimestamp(mtime).strftime("%Y/%m/%d %H:%M:%S")
out.append((name,mdate))
for name, mdate in out:
print(f"{name: >{namel}} -- {mdate}")
def do_clear(params):
msg = f"clear"
print(query(msg))
def do_del(params):
if len(params) == 0:
raise BException("No names to delete")
for name in params:
msg = f"del {name}"
print(query(msg))
def do_put(params):
try:
name,*xs = params
except ValueError:
raise BException("Invalid params for put")
if len(xs) == 0:
payload = sys.stdin.read()
else:
stdin = None
payloads = []
for x in xs:
if x == ".":
payloads.append(paste())
elif x == "-":
if stdin is None:
stdin = sys.stdin.read()
payloads.append(stdin)
elif os.path.exists(x):
with open(x) as f:
payloads.append(f.read())
else:
print(f"Cannot put {x}",file=sys.stderr)
payload = "\n".join(payloads)
msg = f"put {name}\n" + payload
print(query(msg))
def do_append(params):
try:
name,*xs = params
except ValueError:
raise BException("Invalid params for put")
if len(xs) == 0:
payload = sys.stdin.read()
else:
stdin = None
payloads = []
for x in xs:
if x == ".":
payloads.append(paste())
elif x == "-":
if stdin is None:
stdin = sys.stdin.read()
payloads.append(stdin)
elif os.path.exists(x):
with open(x) as f:
payloads.append(f.read())
else:
print(f"Cannot put {x}",file=sys.stderr)
payload = "\n".join(payloads)
msg = f"append {name}\n" + payload
print(query(msg))
def do_quit(name):
print(query("quit"))
def query(msg):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
data = msg.encode() + b'\x00'
s.sendall(data)
data = b""
while True:
dx = s.recv(1024)
#print(f"Received {len(dx)} total {len(data)}:: {dx}")
if len(dx) == 0:
break
data += dx
try:
msg = data.decode()
except Exception:
msg = "<Failed to decode>"
return msg
class Command:
def __init__(self,func,helptext):
self.func = func
self.helptext = helptext
def __call__(self,*xs,**kw):
return self.func(*xs,**kw)
cmds = {
"get": Command(do_get, "Get contents of named clipbaord"),
"clear": Command(do_clear, "Delete all clips"),
"save": Command(do_save, "Dump all clips to file local to server"),
"load": Command(do_load, "Load all cliips from file local to server"),
"put": Command(do_put, "Store text to a clip"),
"append": Command(do_append, "Append text to a clip"),
"del": Command(do_del, "Delete a single clip"),
"list": Command(do_list, "List clips"),
"listsaved": Command(do_listsaved, "List saved clips"),
"quit": Command(do_quit, "Tell server to terminate")
}
if __name__ == "__main__":
main()
The Server
bsrv
#!/usr/bin/env python
import socket
import sys
import json
import os
from datetime import datetime
from glob import glob
from setproctitle import setproctitle
setproctitle("bsrv")
HOST = "0.0.0.0" # Standard loopback interface address (localhost)
PORT = 9999 # Port to listen on (non-privileged ports are > 1023)
bclip_path = os.path.expanduser("~/.bclip")
clips = {}
def load_latest():
if not os.path.isdir(bclip_path):
print("No bclip path")
return
cs = glob(os.path.join(bclip_path,"*.json"))
if len(cs) == 0:
print("No saved clips in bclip path")
return
css = list(sorted(cs, key = lambda t: os.path.getmtime(t)))
latest = css[0].split("/")[-1][:-len(".json")]
result = load_from(latest)
print("Load from latest result:",result)
def main():
args = sys.argv[1:]
if "-l" in args:
print("Latest requested")
args.pop(args.index("-l"))
load_latest()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind((HOST, PORT))
s.listen()
try:
while True:
print("Waiting")
conn, addr = s.accept()
with conn:
if handle(conn,addr) == False:
break
except KeyboardInterrupt: print("KeyboardInterrupt")
def handle(conn,addr):
print(f"Connected by {addr}")
data = b""
while True:
dx = conn.recv(1024)
print(f"Received {len(dx)}")
data += dx
if len(dx) == 0:
break
if b'\x00' in data:
break
print(f"Received {len(data)} in total")
data = data.split(b'\x00')[0]
try:
txt = data.decode()
except Exception():
print(f"Failed to decode {data}")
return
xs = txt.split("\n",1)
x = xs[0]
if x == "quit":
print("Quit")
handle_quit(conn,addr)
print("Return False")
return False
elif x == "list":
handle_list(conn,addr)
elif x == "listsaved":
handle_listsaved(conn,addr)
elif x == "clear":
handle_clear(conn,addr)
elif x.startswith("load "):
handle_load(conn,addr,x)
elif x.startswith("save "):
handle_save(conn,addr,x)
elif x.startswith("get "):
handle_get(conn,addr,x)
elif x.startswith("put "):
y = xs[1]
handle_put(conn,addr,x,y)
elif x.startswith("append "):
y = xs[1]
handle_append(conn,addr,x,y)
elif x.startswith("del "):
handle_del(conn,addr,x)
else:
print(f"Invalid request no match '{x}'")
handle_invalid(conn,addr,x)
return True
def handle_quit(conn,addr):
msg = "Quitting\n"
data = msg.encode()
conn.sendall(data)
def handle_invalid(conn,addr,x):
msg = f"Invalid\n"
data = msg.encode()
conn.sendall(data)
def handle_get(conn,addr,x):
get, name = x.split(" ",1)
name = name.strip()
if not name in clips:
result = ""
else:
result = clips[name]
data = result.encode()
print(f"Sending {len(data)} bytes")
conn.sendall(data)
def handle_save(conn,addr,x):
bclip_path = os.path.expanduser("~/.bclip")
try:
if "/" in x:
raise Exception("can't have / in name")
get, name = x.split(" ",1)
name = name.strip()
fn = name + ".json"
os.makedirs(bclip_path,exist_ok=True)
with open(os.path.join(bclip_path,fn),"wt") as f:
json.dump(clips,f)
result = f"Saved to {name}"
except Exception as e:
result = f"Exception: {e.__class__.__name__} {e}"
data = result.encode()
print(f"Sending {len(data)} bytes")
conn.sendall(data)
def load_from(name):
if "/" in name:
raise Exception("can't have / in name")
fn = name + ".json"
path = os.path.join(bclip_path,fn)
with open(path) as f:
data = json.load(f)
for k,v in data.items():
clips[k] = v
print(f"Loaded {k}")
return f"Loaded from {name}"
def handle_load(conn,addr,x):
try:
if "/" in x:
raise Exception("can't have / in name")
get, name = x.split(" ",1)
name = name.strip()
result = load_from(name)
except FileNotFoundError:
result = f"No stored data named {name}"
except Exception as e:
result = f"Exception: {e.__class__.__name__} {e}"
data = result.encode()
print(f"Sending {len(data)} bytes")
conn.sendall(data)
def handle_del(conn,addr,x):
get, name = x.split(" ",1)
name = name.strip()
if not name in clips:
result = "Not found"
else:
del(clips[name])
result = f"Deleted {name}"
data = result.encode()
print(f"Sending {len(data)} bytes:",data)
conn.sendall(data)
def handle_list(conn,addr):
print(list(clips.keys()))
xs = sorted(clips.keys())
ys = [ f"{k}: ({len(clips[k])} bytes)" for k in xs ]
result = "\n".join(ys)+"\n"
data = result.encode()
print(f"Sending {len(data)} bytes:",data)
conn.sendall(data)
def handle_listsaved(conn,addr):
if not os.path.isdir(bclip_path):
result = "No bclip path"
else:
cs = glob(os.path.join(bclip_path,"*.json"))
cs = list(sorted(cs, key = lambda t: os.path.getmtime(t)))
l = len(".json")
ccs = [ f"{x.split('/')[-1][:-l]},{os.path.getmtime(x)}" for x in cs ]
if len(cs) == 0:
result = "No clips in bclip path"
else:
result = "\n".join(ccs) + "\n"
#result = "\n".join(x[:-l].split("/")[-1] for x in cs) + "\n"
data = result.encode()
print(f"Sending {len(data)} bytes:",data)
conn.sendall(data)
def handle_clear(conn,addr):
global clips
clips = {}
result = "Cleared\n"
data = result.encode()
print(f"Sending {len(data)} bytes:",data)
conn.sendall(data)
def handle_put(conn,addr,x,y,append=False):
put, name = x.split(" ",1)
name = name.strip()
y = y.replace("\r","").strip("\n")+"\n"
clips[name] = y
result = f"Inserted {len(y)} bytes to {name}"
data = result.encode()
print(f"Sending {len(data)} bytes:",data)
conn.sendall(data)
def handle_append(conn,addr,x,y):
put, name = x.split(" ",1)
name = name.strip()
y = y.replace("\r","").strip("\n")+"\n"
if not name in clips:
clips[name] = y
else:
clips[name] += y
result = f"Appended {len(y)} bytes to {name}"
data = result.encode()
print(f"Sending {len(data)} bytes:",data)
conn.sendall(data)
if __name__ == "__main__":
main()