replace () {
# copy the initial bit of the current line to $tmp.1
$dd if=$ed.c of=$tmp.1 bs=1 "count=$1" 2>/dev/null
# from the rest of the file, remove also the characters to be
# replaced and save the result in $tmp.2
( $dd if=$ed.c "skip=$1" bs=1 | $dd of=$tmp.2 bs=1 "skip=$2" ) 2>/dev/null
# remove the two numbers from the argument list
shift
shift
# copy $tmp.1, the remaining arguments, $tmp.2 to $tmp.3
( $dd if=$tmp.1; Echo -n "$@"; $dd if=$tmp.2 ) > $tmp.3 2>/dev/null
# save $tmp.3 as the new current line
$dd if=$tmp.3 of=$ed.c 2>/dev/null
}
To search for a string, we can use the shell's pattern matching. However,
this won't tell us where the string is, unless we use a pattern which only
finds it at the beginning of line.
If the string is not there, we try shifting a byte to a temporary file, and repeat until we either find the string of finish the bytes. In the latter case we return false, in the former we remove the search string, and copy the bytes shifted out, plus the replacement text, back at the beginning of the current line.
If we need to replace the n-th occurrence, all we need to do is to count the matches and only do the replacement after we have seen the required number of them.
We have an invisible cursor moving through the current line. The file
$tmp.4 contains the text before the cursor, and
$tmp.5 the text after it. The variable n
contains the number of matches we need to find - it is decremented
when we find a match, and, if it goes to 0, we do the replacement.
rstring () {
n="$1"
shift;
string="$1"
shift
# initially, $tmp.4 is empty and $tmp.5 is the whole current line
$dd if=/dev/null of=$tmp.4 2>/dev/null
$dd if=$ed.c of=$tmp.5 2>/dev/null
# we exit when we find end of file or a match
while true
do
# look at the start of $tmp.5
case "`$dd if=$tmp.5 2>/dev/null`" in
$string*)
# a match -- if $n < 2, we do the replacement
if lt $n 2
then
# save the replacement text to $tmp.2
# and the string to $tmp.1 - we need this to find out its length
Echo -n "$@" > $tmp.2
Echo -n "$string" > $tmp.1
# length of replacement string?
IFS="+"
set `$dd bs=1 if=$tmp.1 of=/dev/null 2>&1`
IFS="$saveIFS"
slen=$1
IFS="+"
# copy text from $tmp.4 (text before match), followed by
# $tmp.2 (replacement) and then $tmp.5 (text after match);
# from the latter, we skip $slen bytes to remove the text
# we matched
( $dd if=$tmp.4; $dd if=$tmp.2; $dd if=$tmp.5 bs=1 skip=$slen ) \
2>/dev/null > $tmp
# and copy the resulting file back to the current line
$dd if=$tmp of=$ed.c 2>/dev/null
# return OK
return 0
else
# a match, but $n > 1: subtract 1, move the first byte of the
# match to $tmp.4 and continue
subtract n $n 1
# $tmp.4 followed by first byte of $tmp.5 goes to $tmp
( $dd if=$tmp.4; $dd if=$tmp.5 bs=1 count=1 ) > $tmp 2>/dev/null
# and then back to $tmp.4
$dd if=$tmp of=$tmp.4 2>/dev/null
# remove first byte 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
;;
?*) # need to keep looking - move one byte from $tmp.5 to $tmp.4
# $tmp.4 followed by first byte of $tmp.5 goes to $tmp
( $dd if=$tmp.4; $dd if=$tmp.5 bs=1 count=1 ) > $tmp 2>/dev/null
# and then back to $tmp.4
$dd if=$tmp of=$tmp.4 2>/dev/null
# remove first byte 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
;;
*) # end of file - return error
return 1
;;
esac
done
}