# 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] }