# Plan 9 rc shell functions
# ============================================================================
# acme
# ============================================================================
# B -- plumb files for editing
fn B {
wdir=`{pwd}
for(i in $*){
if(! ~ $i /*)
i=$wdir/$i
plumb -s B -d edit $i
}
}
# E -- edit file, wait for save
fn E {
if(! ~ $#* 1){
echo usage: E file >[1=2]
exit usage
}
if(! test -e $1){
echo E: $1: no such file >[1=2]
exit 'no file'
}
otm=`{mtime $1 | awk '{print $1}'}
B $1
while(~ $otm `{mtime $1 | awk '{print $1}'})
sleep 1
}
# awd -- update acme window label with cwd
fn awd {
if(test -f /dev/label){
p=`{pwd}
echo -n $p/-$sysname > /dev/label >[2]/dev/null
}
}
# ============================================================================
# dev
# ============================================================================
# b -- build, install, clean cycle
fn b {
if(~ $#* 0)
mk install && mk clean
if not
for(d in $*) @{
cd $d
mk install
mk clean
}
}
# bt -- build and test
fn bt {
mk install && mk clean && {
if(test -d test)
mk test
if not if(test -f test.rc)
rc test.rc
}
}
# fndef -- find C function definitions
fn fndef {
grep -rn '^'$1'(' ${2:-.} | grep -v '/\*' | grep '{'
}
# callers -- find all call sites for a function
fn callers {
grep -rn $1'(' ${2:-.} | grep -v '^[^:]*:[^:]*:.*^'$1
}
# hdr -- find which header declares a function
fn hdr {
grep -rn $1 /sys/include /$objtype/include | head -10
}
# loc -- count lines of code
fn loc {
dir=${1:-.}
echo -n 'C: '; wc -l $dir/*.[ch] >[2]/dev/null | tail -1
echo -n 'rc: '; wc -l $dir/*.rc >[2]/dev/null | tail -1
}
# longlines -- find lines over N columns
fn longlines {
n=${1:-80}
shift
for(f in $*)
awk 'length > '$n' {print FILENAME ":" NR ": " length " cols"}' $f
}
# cleanws -- strip trailing whitespace
fn cleanws {
for(f in $*){
sed 's/[ \t]*$//' $f >/tmp/cleanws.$pid
if(! cmp -s $f /tmp/cleanws.$pid)
cp /tmp/cleanws.$pid $f
rm -f /tmp/cleanws.$pid
}
}
# tabcheck -- find files using spaces for indentation
fn tabcheck {
dir=${1:-.}
grep -rn '^ [^ *]' $dir/*.[ch] | head -20
}
# watch -- poll for changes and rebuild
fn watch {
last=''
while(){
sig=`{ls -l *.[ch] mkfile | md5sum}
if(! ~ $"sig $"last){
echo rebuilding...
mk install >[2=1] || echo BUILD FAILED
last=$sig
}
sleep 2
}
}
# ============================================================================
# doc
# ============================================================================
# ms2pdf -- compile ms document to PDF
fn ms2pdf {
if(~ $#* 0){
echo 'usage: ms2pdf input.ms [output.pdf]' >[1=2]
exit usage
}
input=$1
if(~ $#* 1)
output=`{echo $input | sed 's/\.ms$/.pdf/'}
if not
output=$2
pic $input | tbl | eqn | troff -ms | dpost | ps2pdf >$output
}
# docview -- format and view a document
fn docview {
pic $1 | tbl | eqn | troff -ms | page
}
# spellcheck -- check spelling in a troff document
fn spellcheck {
deroff $* | spell
}
# spellctx -- misspelled words with context
fn spellctx {
misspelled=`{deroff $1 | spell}
for(word in $misspelled){
echo '---' $word '---'
grep -n $word $1
}
}
# readlevel -- approximate reading level
fn readlevel {
if(~ $#* 0)
input=/fd/0
if not
input=$1
deroff $input | awk '
{
for(i=1;i<=NF;i++){
words++
w=$i
gsub(/[^aeiouAEIOU]/,"",w)
if(length(w)==0) w="a"
syls+=length(w)
}
n=gsub(/[.!?]/,"&")
sents+=n
}
END{
if(words==0||sents==0){print "insufficient text";exit}
fk=0.39*(words/sents)+11.8*(syls/words)-15.59
printf "Words: %d\n",words
printf "Sentences: %d\n",sents
printf "Avg words/sentence: %.1f\n",words/sents
printf "Flesch-Kincaid Grade: %.1f\n",fk
}'
}
# ============================================================================
# file
# ============================================================================
# lc -- columnar listing
fn lc {
ls -pF $* | mc
}
# ff -- find files by pattern
fn ff {
walk -f | grep $*
}
# rgrep -- recursive grep
fn rgrep {
grep $* `{walk -f}
}
# bigfiles -- find files over 100MB
fn bigfiles {
walk -f -esp | awk '$1>100000000'
}
# today -- files modified today
fn today {
walk -emp | awk '$1>'^`{date -n}^'-(3600*12)'
}
# dircp -- copy directory tree
fn dircp {
switch($#*){
case 2
@{builtin cd $1 && tar cif /fd/1 .} | @{builtin cd $2 && tar xTf /fd/0}
case *
echo usage: dircp from to >[1=2]
exit usage
}
}
# runeinfo -- show bytes vs runes for files
fn runeinfo {
for(f in $*){
bytes=`{wc -c <$f}
runes=`{wc -r <$f}
lines=`{wc -l <$f}
echo $f: $bytes bytes, $runes runes, $lines lines
}
}
# ============================================================================
# git
# ============================================================================
# smart git: no args = status
fn g {
if(~ $#* 0)
git status -sb
if not
git $*
}
# jump to git root
fn gr {
cd `{git rev-parse --show-toplevel}
}
# today's commits
fn gtoday {
git log --since=midnight --author=`{git config user.name} --oneline
}
# show remote url
fn gurl {
git config --get remote.origin.url
}
# interactive branch checkout (requires fzf)
fn gbr {
branch=`{git branch -a | sed 's/^..//' | fzf}
if(! ~ $#branch 0)
git checkout $branch
}
# WIP commit
fn gwip {
git add -A
git commit -m 'WIP: '^`{date +%H:%M}
}
# ============================================================================
# nav
# ============================================================================
# mkdir + cd
fn mkd {
mkdir -p $1
cd $1
}
# go up N directories
fn up {
n=$1
if(~ $#n 0) n=1
d=''
i=0
while(test $i -lt $n){
d=$d^../
i=`{expr $i + 1}
}
cd $d
}
# go to src directory or subdirectory
fn src {
if(~ $#* 0)
cd $home/src
if not
cd $home/src/$1
}
# ============================================================================
# net
# ============================================================================
# doi2bib -- DOI to BibTeX
fn doi2bib {
hget -r 'Accept: application/x-bibtex' 'https://doi.org/'^$1
}
# webpaste -- paste text to pastebin
fn webpaste {
if(~ $#* 0)
file=/fd/0
if not
file=$1
hpost -u http://okturing.com -p / a_body@$file submit:submit fake:fake a_func:add_post url: | grep -e '/body"' | sed 1q | sed 's/^.*href="//g; s/body".*$/body/g'
}
# ============================================================================
# proc
# ============================================================================
# k -- kill processes by name (pipe output to rc to execute)
fn k {
for(i in $*){
ps | sed -n '/^'$user' .* '$i'$/s%[^ ]* *%~>/proc/%
s% *.* (.*)%/note} # \1%
s%~%@{echo kill%p'
}
}
# broke -- find broken processes
fn broke {
ps | sed -n '/^'$user' .* Broken/s%[^ ]* *%~>/proc/%
s% *.* (.*)%/ctl # \1%
s%~%echo kill%p'
}
# psu -- processes for current user
fn psu {
ps | sed -n '/^'$user' /p'
}
# who -- list logged-in users
fn who {
ps | sed '/Broken/d; /Exiting/d; s% .*$%%' | sort -u
}
# ============================================================================
# sam
# ============================================================================
# samrename -- rename variable safely (avoiding strings/comments)
fn samrename {
if(~ $#* 0 1 2){
echo 'usage: samrename old new file' >[1=2]
exit usage
}
echo ',y/"[^"]*"/ y/''[^'']*''/ x/[a-zA-Z_0-9]+/ g/^'^$1^'$/ c/'^$2^'/
w
q' | sam -d $3
}
# samextract -- extract all matches of a pattern
fn samextract {
echo ',x/'^$1^'/ p
q' | sam -d $2
}
# samindent -- indent every line by 4 spaces
fn samindent {
ssam ',x/.*\n/ s/^/ /' <$1
}
# samjoin -- join continuation lines (backslash-newline)
fn samjoin {
echo ',s/\\\n//g
w
q' | sam -d $1
}
# samtabs -- convert tabs to spaces
fn samtabs {
echo ',s/\t/ /g
w
q' | sam -d $1
}
# samurls -- extract all URLs from a file
fn samurls {
ssam -n ',x/https?:\/\/[^ \t\n<>"]+/ p' <$1
}
# samdups -- find doubled words
fn samdups {
ssam -n ',x/\b([a-zA-Z]+)[ \n]+\1\b/ p' <$1
}
# ============================================================================
# text
# ============================================================================
# gsub -- global substitution across files
fn gsub {
if(~ $#* 0 1 2){
echo 'usage: gsub old new files...' >[1=2]
exit usage
}
old=$1; new=$2; shift 2
for(f in $*){
echo ',s/'^$old^'/'^$new^'/g
w
q' | sam -d $f
}
}
# delblank -- remove blank lines
fn delblank {
echo ',x/\n\n+/c/\n/
w
q' | sam -d $1
}
# deltws -- delete trailing whitespace
fn deltws {
echo ',x/[ \t]+\n/ c/\n/
w
q' | sam -d $1
}
# delcomments -- delete C-style block comments
fn delcomments {
echo ',s,/\*[^*]*\*+([^/*][^*]*\*+)*/,,g
w
q' | sam -d $1
}
# keeplines -- keep only lines matching pattern
fn keeplines {
echo ',x/.*\n/ v/'^$1^'/ d
w
q' | sam -d $2
}
# dellines -- delete lines matching pattern
fn dellines {
echo ',x/.*\n/ g/'^$1^'/ d
w
q' | sam -d $2
}
# extractpara -- extract paragraphs containing a word
fn extractpara {
echo ',x/(.+\n)+/ g/'^$1^'/ p
q' | sam -d $2
}
# revlines -- reverse line order
fn revlines {
echo ',x/.*\n/ m0
w
q' | sam -d $1
}
# headings -- extract markdown headings
fn headings {
echo ',x/^#+.*\n/ p
q' | sam -d $1
}
# wordfreq -- word frequency count
fn wordfreq {
deroff $* | tr -cs 'a-zA-Z' '\n' | tr 'A-Z' 'a-z' | sort | uniq -c | sort -rn
}
# vocabsize -- count unique words
fn vocabsize {
deroff $* | tr -cs 'a-zA-Z' '\n' | tr 'A-Z' 'a-z' | sort -u | wc -l
}
# sentlen -- sentence length distribution
fn sentlen {
deroff $* | awk 'BEGIN{RS="[.!?]"} NF>0{print NF}' | sort -n | uniq -c | sort -rn
}
# mswc -- manuscript word count (troff-aware)
fn mswc {
words=`{deroff $* | wc -w}
pages=`{echo $words / 250 | bc}
echo $words words, approximately $pages manuscript pages
}
# ============================================================================
# util
# ============================================================================
# extract archive (any format)
fn extract {
if(! test -f $1){
echo ''''^$1^'''' is not a file
exit 'not a file'
}
switch($1){
case *.tar.gz *.tgz
tar xzf $1
case *.tar.bz2 *.tbz2
tar xjf $1
case *.tar.xz *.txz
tar xJf $1
case *.tar
tar xf $1
case *.gz
gunzip $1
case *.bz2
bunzip2 $1
case *.xz
xz -d $1
case *.zip
unzip $1
case *.7z
7z x $1
case *
echo 'cannot extract '^$1
}
}
# file/dir size
fn fs {
du -sh $1
}
# quick HTTP server
fn serve {
port=$1
if(~ $#port 0) port=8000
echo 'Serving on http://localhost:'^$port
python3 -m http.server $port --bind 127.0.0.1
}
# clipboard (cross-platform)
fn clip {
if(whatis xclip >/dev/null >[2=1])
xclip -selection clipboard
if not if(whatis pbcopy >/dev/null >[2=1])
pbcopy
if not
echo 'no clipboard tool'
}
# weather
fn wttr {
curl -s 'wttr.in/'^$1^'?format=v2'
}
# command exists check
fn has {
whatis $1 >/dev/null >[2=1]
}