Con GNU sed, reemplazando todos los espacios en eol por guiones bajos:
sed ':x;s/ \( *\)$/_\1/;tx'
Más eficiente para usar perl:
perl -lpe 's/(\s+)$/"_" x length($1)/e' input.txt
que solo tiene que hacer una sustitución por línea con espacios en blanco al final, en lugar de hacer un bucle.
Con GNU awk para que el tercer argumento coincida() y gensub():
$ awk 'match($0,/(.*[^ ])(.*)/,a){$0=a[1] gensub(/ /,"_","g",a[2])} 1' file
foo bar_____
foo bar oof
line 3a___
line fo a_
Con cualquier awk:
$ awk 'match($0,/ +$/){tail=substr($0,RSTART,RLENGTH); gsub(/ /,"_",tail); $0=substr($0,1,RSTART-1) tail} 1' file
foo bar_____
foo bar oof
line 3a___
line fo a_
Para reemplazar los espacios en blanco iniciales también modificando la solución de Gawk anterior:
$ awk 'match($0,/^( *)(.*[^ ])(.*)/,a){$0=gensub(/ /,"_","g",a[1]) a[2] gensub(/ /,"_","g",a[3])} 1' file
foo bar_____
_foo bar oof
__line 3a___
__line fo a_