On a GNU system, to substitute one character (other than newline) at random, you could do:
offset=$(grep -bo . < "$file" | cut -d: -f1 | shuf -n1)
[ -z "$offset" ] || # file doesn't have non-newline characters
printf c | dd bs=1 seek="$offset" of="$file" conv=notrunc status=none
(with old versions of GNU
dd (prior to 8.20), replace
grep -bo . < "$file" would give you the offset in number of bytes in the file of each non-newline character. For instance, with a file encoded in UTF-8 that contains:
That gives us:
$ grep -bo . < "$file"
cut -d: -f1, we retain the part before the first colon. Then, we pick one of those offsets at random with
That assumes the replacement character has the same size as the replaced one. For instance, replacing that £ above (2 bytes) with c (1 byte) would leave the file with
c followed by an invalid character.
To work around that, we can't overwrite the file in-place anymore as we'd need to shift data around.
We'd need something like:
perl -C -0777 -pi -e "substr \$_, $offset, 1, 'c'" -- "$file"
perl honours the locale for what constitutes a character.
-0777 -p turns on the slurp mode where the content of
$file is slurped into
$_ (see Security implications of running perl -ne '…' * though for security considerations with that construct).
-pi gives you in-place editing,
$_ is written back to the file after the code is run. Then we call
substr to substitute the 1 character at the given offset with