dd-ex.sh   [plain text]


#!/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 <<EOF 
$@
EOF
      IFS="+"
      set `$dd if=$tmp bs=1 of=/dev/null skip=1 2>&1`
      IFS="$saveIFS"
      $dd if=$tmp bs=1 count=$1 2>/dev/null
      ;;
  *)  $dd 2>/dev/null <<EOF 
$@
EOF
      ;;
esac
}

# this is used to generate garbage files

true () {
  return 0
}

false () {
  return 1
}

zero () {
  ( trap 'go=false' 13
    go=true
    while $go
    do
      $dd "if=$0"
      case "$?" in
	0) ;;
	*) go=false ;;
      esac
    done
  ) 2>/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 n1<n2, gt if n1>n2, 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 <<EOF
$@
EOF
  while :
  do
    case "`$dd if=$tmp.5 2>/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