#!/bin/sh # this is a line editor using only /bin/sh, /bin/dd and /bin/rm # /bin/rm is not really required, but it is nice to clean up temporary files PATH= dd=/bin/dd rm=/bin/rm # temporary files we might need tmp=/tmp/silly.$$ ed=/tmp/ed.$$ trap "$rm -f $tmp $tmp.1 $tmp.2 $tmp.3 $tmp.4 $tmp.5 $tmp.6 $ed.a $ed.b $ed.c; exit" 0 1 2 3 # from now on, no more rm - the above trap is enough unset rm # we do interesting things with IFS, but better save it... saveIFS="$IFS" # in case "echo" is not a shell builtin... Echo () { case "$1" in -n) shift $dd of=$tmp 2>/dev/null <&1` IFS="$saveIFS" $dd if=$tmp bs=1 count=$1 2>/dev/null ;; *) $dd 2>/dev/null </dev/null } # arithmetic using dd! # add variable n1 n2 n3... # assigns n1+n2+n3+... to variable add () { result="$1" shift $dd if=/dev/null of=$tmp bs=1 2>/dev/null for n in "$@" do case "$n" in 0) ;; *) zero | $dd of=$tmp.1 bs=1 "count=$n" 2>/dev/null ( $dd if=$tmp; $dd if=$tmp.1 ) 2>/dev/null | $dd of=$tmp.2 2>/dev/null $dd if=$tmp.2 of=$tmp 2>/dev/null ;; esac done IFS="+" set `$dd if=$tmp bs=1 of=/dev/null 2>&1` IFS="$saveIFS" eval $result='$1' } # subtract variable n1 n2 # subtracts n2 from n1, assigns result to variable subtract () { result="$1" zero | $dd of=$tmp bs=1 "count=$2" 2>/dev/null IFS="+" set `$dd if=$tmp bs=1 of=/dev/null "skip=$3" 2>&1` IFS="$saveIFS" case "$1" in dd*) set 0 ;; esac eval $result='$1' } # multiply variable n1 n2 # variable = n1 * n2 multiply () { result="$1" zero | $dd "bs=$2" of=$tmp "count=$3" 2>/dev/null IFS="+" set `$dd if=$tmp bs=1 of=/dev/null 2>&1` IFS="$saveIFS" eval $result='$1' } # divide variable n1 n2 # variable = int( n1 / n2 ) divide () { result="$1" zero | $dd bs=1 of=$tmp "count=$2" 2>/dev/null IFS="+" set `$dd if=$tmp "bs=$3" of=/dev/null 2>&1` IFS="$saveIFS" eval $result='$1' } # compare variable n1 n2 sets variable to lt if n1n2, eq if n1==n2 compare () { res="$1" n1="$2" n2="$3" subtract somename "$n1" "$n2" case "$somename" in 0) ;; *) eval $res=gt; return; esac subtract somename "$n2" "$n1" case "$somename" in 0) ;; *) eval $res=lt; return; esac eval $res=eq } # lt n1 n2 returns true if n1 < n2 lt () { n1="$1" n2="$2" subtract somename "$n2" "$n1" case "$somename" in 0) return 1 ;; esac return 0 } # le n1 n2 returns true if n1 <= n2 le () { n1="$1" n2="$2" subtract somename "$n1" "$n2" case "$somename" in 0) return 0 ;; esac return 1 } # gt n1 n2 returns true if n1 > n2 gt () { n1="$1" n2="$2" subtract somename "$n1" "$n2" case "$somename" in 0) return 1 ;; esac return 0 } # ge n1 n2 returns true if n1 >= n2 ge () { n1="$1" n2="$2" subtract somename "$n2" "$n1" case "$somename" in 0) return 0 ;; esac return 1 } # useful functions for the line editor # open a file - copy it to the buffers open () { file="$1" set `$dd "if=$file" of=/dev/null 2>&1` case "$1" in dd*) return 1 esac # copy the first line to $ed.c go=true len=0 while $go do case "`$dd "if=$file" bs=1 skip=$len count=1 2>/dev/null`" in ?*) go=true ;; *) go=false ;; esac add len 1 $len done # now $len is the length of the first line (including newline) $dd "if=$file" bs=1 count=$len of=$ed.c 2>/dev/null $dd "if=$file" bs=1 skip=$len of=$ed.b 2>/dev/null $dd if=/dev/null of=$ed.a 2>/dev/null lineno=1 } # save a file - copy the buffers to the file save () { # make a backup copy of the original $dd "if=$1" "of=$1.bak" 2>/dev/null # and save ( $dd if=$ed.a; $dd if=$ed.c; $dd if=$ed.b ) > "$1" 2>/dev/null } # replace n1 n2 bla replaces n2 chars of current line, starting n1-th replace () { $dd if=$ed.c of=$tmp.1 bs=1 "count=$1" 2>/dev/null ( $dd if=$ed.c "skip=$1" bs=1 | $dd of=$tmp.2 bs=1 "skip=$2" ) 2>/dev/null shift shift ( $dd if=$tmp.1; Echo -n "$@"; $dd if=$tmp.2 ) > $tmp.3 2>/dev/null $dd if=$tmp.3 of=$ed.c 2>/dev/null } # rstring n s bla # replace the n-th occurence of s with bla rstring () { n="$1" shift; # first we have to find it - this is fun! # we have $tmp.4 => text before string, $tmp.5 => text after $dd if=/dev/null of=$tmp.4 2>/dev/null $dd if=$ed.c of=$tmp.5 2>/dev/null string="$1" shift $dd of=$tmp.6 2>/dev/null </dev/null`" in $string*) if lt $n 2 then # now we want to replace the string Echo -n "$@" > $tmp.2 Echo -n "$string" > $tmp.1 IFS="+" set `$dd bs=1 if=$tmp.1 of=/dev/null 2>&1` IFS="$saveIFS" slen=$1 IFS="+" ( $dd if=$tmp.4; $dd if=$tmp.2; $dd if=$tmp.5 bs=1 skip=$slen ) \ 2>/dev/null > $tmp $dd if=$tmp of=$ed.c 2>/dev/null return 0 else subtract n $n 1 ( $dd if=$tmp.4; $dd if=$tmp.5 bs=1 count=1 ) > $tmp 2>/dev/null $dd if=$tmp of=$tmp.4 2>/dev/null # and remove it from $tmp.5 $dd if=$tmp.5 of=$tmp bs=1 skip=1 2>/dev/null $dd if=$tmp of=$tmp.5 2>/dev/null fi ;; ?*) # add one more byte... ( $dd if=$tmp.4; $dd if=$tmp.5 bs=1 count=1 ) > $tmp 2>/dev/null $dd if=$tmp of=$tmp.4 2>/dev/null # and remove it from $tmp.5 $dd if=$tmp.5 of=$tmp bs=1 skip=1 2>/dev/null $dd if=$tmp of=$tmp.5 2>/dev/null ;; *) # not found return 1 ;; esac done } # skip to next line next () { add l $lineno 1 ( $dd if=$ed.a; $dd if=$ed.c ) 2>/dev/null > $tmp.3 $dd if=$ed.b of=$tmp.4 2>/dev/null open $tmp.4 $dd if=$tmp.3 of=$ed.a 2>/dev/null lineno=$l } # delete current line delete () { l=$lineno $dd if=$ed.a 2>/dev/null > $tmp.1 $dd if=$ed.b of=$tmp.2 2>/dev/null open $tmp.2 $dd if=$tmp.1 of=$ed.a 2>/dev/null lineno=$l } # insert before current line (without changing current) insert () { ( $dd if=$ed.a; Echo "$@" ) 2>/dev/null > $tmp.1 $dd if=$tmp.1 of=$ed.a 2>/dev/null add lineno $lineno 1 } # previous line prev () { case "$lineno" in 1) ;; *) subtract lineno $lineno 1 # read last line of $ed.a IFS='+' set `$dd if=$ed.a of=/dev/null bs=1 2>&1` IFS="$saveIFS" size=$1 # empty? case "$size" in 0) return ;; esac subtract size $size 1 # skip final newline case "$size" in 0) ;; *) subtract size1 $size 1 case "`$dd if=$ed.a bs=1 skip=$size count=1 2>/dev/null`" in ?*) ;; *) size=$size1 ;; esac ;; esac go=true while $go do case "$size" in 0) go=false ;; *) case "`$dd if=$ed.a bs=1 skip=$size count=1 2>/dev/null`" in ?*) go=true; subtract size $size 1 ;; *) go=false; add size $size 1 ;; esac ;; esac done # now $size is the size of the first n-1 lines # add $ed.c to $ed.b ( $dd if=$ed.c; $dd if=$ed.b ) 2>/dev/null > $tmp.5 $dd if=$tmp.5 of=$ed.b 2>/dev/null # move line to ed.c case "$size" in 0) $dd if=$ed.a of=$ed.c 2>/dev/null $dd if=/dev/null of=$tmp.5 2>/dev/null ;; *) $dd if=$ed.a of=$ed.c bs=1 skip=$size 2>/dev/null $dd if=$ed.a of=$tmp.5 bs=1 count=$size 2>/dev/null ;; esac # move rest to ed.a $dd if=$tmp.5 of=$ed.a 2>/dev/null ;; esac } # goes to a given line goto () { rl="$1" compare bla "$rl" $lineno case "$bla" in eq) return ;; gt) while gt "$rl" $lineno do next done ;; lt) while lt "$rl" $lineno do prev done ;; esac } lineout () { Echo -n "$lineno: " $dd if=$ed.c 2>/dev/null } state=closed name= autoprint=true while true do Echo -n '> ' read cmd arg case "$cmd:$state" in open:open) Echo "There is a file open already" ;; open:*) if open "$arg" then state=open; name="$arg"; $autoprint else Echo "Cannot open $arg" fi ;; new:open) Echo "There is a file open already" ;; new:*) open "$arg" state=open name="$arg" $autoprint ;; close:changed) Echo "Use 'discard' or 'save'" ;; close:closed) Echo "Closed already" ;; close:*) state=closed ;; save:closed) Echo "There isn't a file to save" ;; save:*) case "$arg" in ?*) save "$arg" ;; *) save "$name" ;; esac state=open ;; discard:changed) Echo "Your problem!"; state=closed ;; discard:*) state=closed ;; print:closed) Echo "No current file" ;; print:*) lineout ;; goto:closed) Echo "No current file" ;; goto:*) goto "$arg"; $autoprint ;; next:closed) Echo "No current file" ;; next:*) next; $autoprint ;; prev:closed) Echo "No current file" ;; prev:*) prev; $autoprint ;; name:closed) Echo "No current file" ;; name:*) name="$arg" ;; replace:closed) Echo "No current file" ;; replace:*) if rstring 1 $arg then state=changed; $autoprint else Echo "Not found" fi ;; nreplace:closed) Echo "No current file" ;; nreplace:*) if rstring $arg then state=changed; $autoprint else Echo "Not found" fi ;; delete:closed) Echo "No current file" ;; delete:*) delete; state=changed; $autoprint ;; insert:closed) Echo "No current file" ;; insert:*) insert "$arg"; prev; state=changed; $autoprint ;; quit:changed) Echo "Use 'save' or 'discard'" ;; quit:*) Echo "bye"; exit;; autoprint:*) autoprint="lineout" ;; noprint:*) autoprint="" ;; :*) ;; *) Echo "Command not understood" ;; esac done