Skip to main content
deleted 4 characters in body
Source Link
user313992
user313992

With awk

awk -F '[ \t]+$' 'NF>1{t=substr($0,length($1)+1);gsub(/./,"_",t); $0=$1 t} 1'

This also handles a mixture of trailing tabs and spaces. The field separator (-F, FS) can be easily adjusted to only match spaces or also match other kind of invisible characters, provided that it's kept anchored at the end with $.

To make that work for leading blanks, everything should be mirrored not just $ to ^:

awk -F '^[ \t]+' 'NF>1{h=substr($0,1,length()-length($2));gsub(/./,"_",h); $0=h $2} 1'

To make it work for both leading and trailing blanks, the logic should be inverted; set the field separator to a pattern not matching leading and trailing blanks:

awk -F '[^ \t](.*[^ \t]|$)' '{s=$0; h=gsub(/./,"_",$1); t=gsub(/./,"_",$2); print $1 substr(s,h+1, length(s)-h-t) $2}'

Or the same with adjustable pattern:

awk -v ns='[^ \t]' 'BEGIN{FS=ns"(.*"ns"|$)"}{s=$0; h=gsub(/./,"_",$1); t=gsub(/./,"_",$2); print $1 substr(s,h+1, length(s)-h-t) $2}'

Different from @EdMorton's solutions, these handle correctly lines which contain only spaces and will work with any implementation of awk, not just GNU awk (gawk): eg. with mawk or bwk ("original-awk"), which are both much faster than gawk. But even when used with gawk, the last solution will be almost twice as fast as @EdMorton's.

With sed

With sed, the only solution I can think of is to substitute repeatedly in a loop; if there are many trailing spaces and long lines, this can get slow fast:

sed -e :x -e 's/ \( *\)$/_\1/;tx'

Notice that sed ':x;s/ \( *\)$/_\1/;tx' is not standard sed; :label is not one of the commands which can be terminated by a ;:

Editing commands other than {...}, a, b, c, i, r, t, w, :, and # can be followed by a <semicolon>, optional <blank> characters, and another editing command.

With perl

Here is an alternate perl solution, which is absolutely NOT really an improvement upon the existing perl answer, but which, since it doesn't use the e flag of s///, could be theoretically adapted to some other tool providing a sed-like s/// and perl/pcre-like zero-width assertions in its regexes:

perl -ple 's/\s(?=\s*$)/_/g'

With awk

awk -F '[ \t]+$' 'NF>1{t=substr($0,length($1)+1);gsub(/./,"_",t); $0=$1 t} 1'

This also handles a mixture of trailing tabs and spaces. The field separator (-F, FS) can be easily adjusted to only match spaces or also match other kind of invisible characters, provided that it's kept anchored at the end with $.

To make that work for leading blanks, everything should be mirrored not just $ to ^:

awk -F '^[ \t]+' 'NF>1{h=substr($0,1,length()-length($2));gsub(/./,"_",h); $0=h $2} 1'

To make it work for both leading and trailing blanks, the logic should be inverted; set the field separator to a pattern not matching leading and trailing blanks:

awk -F '[^ \t](.*[^ \t]|$)' '{s=$0; h=gsub(/./,"_",$1); t=gsub(/./,"_",$2); print $1 substr(s,h+1, length(s)-h-t) $2}'

Or the same with adjustable pattern:

awk -v ns='[^ \t]' 'BEGIN{FS=ns"(.*"ns"|$)"}{s=$0; h=gsub(/./,"_",$1); t=gsub(/./,"_",$2); print $1 substr(s,h+1, length(s)-h-t) $2}'

Different from @EdMorton's solutions, these handle correctly lines which contain only spaces and will work with any implementation of awk, not just GNU awk (gawk): eg. with mawk or bwk ("original-awk"), which are both much faster than gawk. But even when used with gawk, the last solution will be almost twice as fast as @EdMorton's.

With sed

With sed, the only solution I can think of is to substitute repeatedly in a loop; if there are many trailing spaces and long lines, this can get slow fast:

sed -e :x -e 's/ \( *\)$/_\1/;tx'

Notice that sed ':x;s/ \( *\)$/_\1/;tx' is not standard sed; :label is not one of the commands which can be terminated by a ;:

Editing commands other than {...}, a, b, c, i, r, t, w, :, and # can be followed by a <semicolon>, optional <blank> characters, and another editing command.

With perl

Here is an alternate perl solution, which is absolutely NOT an improvement upon the existing perl answer, but which, since it doesn't use the e flag of s///, could be theoretically adapted to some other tool providing a sed-like s/// and perl/pcre-like zero-width assertions in its regexes:

perl -ple 's/\s(?=\s*$)/_/g'

With awk

awk -F '[ \t]+$' 'NF>1{t=substr($0,length($1)+1);gsub(/./,"_",t); $0=$1 t} 1'

This also handles a mixture of trailing tabs and spaces. The field separator (-F, FS) can be easily adjusted to only match spaces or also match other kind of invisible characters, provided that it's kept anchored at the end with $.

To make that work for leading blanks, everything should be mirrored not just $ to ^:

awk -F '^[ \t]+' 'NF>1{h=substr($0,1,length()-length($2));gsub(/./,"_",h); $0=h $2} 1'

To make it work for both leading and trailing blanks, the logic should be inverted; set the field separator to a pattern not matching leading and trailing blanks:

awk -F '[^ \t](.*[^ \t]|$)' '{s=$0; h=gsub(/./,"_",$1); t=gsub(/./,"_",$2); print $1 substr(s,h+1, length(s)-h-t) $2}'

Or the same with adjustable pattern:

awk -v ns='[^ \t]' 'BEGIN{FS=ns"(.*"ns"|$)"}{s=$0; h=gsub(/./,"_",$1); t=gsub(/./,"_",$2); print $1 substr(s,h+1, length(s)-h-t) $2}'

Different from @EdMorton's solutions, these handle correctly lines which contain only spaces and will work with any implementation of awk, not just GNU awk (gawk): eg. with mawk or bwk ("original-awk"), which are both much faster than gawk. But even when used with gawk, the last solution will be almost twice as fast as @EdMorton's.

With sed

With sed, the only solution I can think of is to substitute repeatedly in a loop; if there are many trailing spaces and long lines, this can get slow fast:

sed -e :x -e 's/ \( *\)$/_\1/;tx'

Notice that sed ':x;s/ \( *\)$/_\1/;tx' is not standard sed; :label is not one of the commands which can be terminated by a ;:

Editing commands other than {...}, a, b, c, i, r, t, w, :, and # can be followed by a <semicolon>, optional <blank> characters, and another editing command.

With perl

Here is an alternate perl solution, which is NOT really an improvement upon the existing perl answer, but which, since it doesn't use the e flag of s///, could be theoretically adapted to some other tool providing a sed-like s/// and perl/pcre-like zero-width assertions in its regexes:

perl -ple 's/\s(?=\s*$)/_/g'
added 18 characters in body
Source Link
user313992
user313992

[since everybody is so territorial and refractory to include any of my suggestions in their answers, I'll collect my nitpicks here]

This also handles a mixture of trailing Tabstabs and Spacesspaces. The field separator (-F, FS regex) can be easily adjusted to only match spaces or also match other kind of invisible characters, provided that it's kept anchored at the end with $.

[since everybody is so territorial and refractory to include any of my suggestions in their answers, I'll collect my nitpicks here]

This also handles a mixture of trailing Tabs and Spaces. The FS regex can be easily adjusted to only match spaces or also match other kind of invisible characters, provided that it's kept anchored at the end with $.

This also handles a mixture of trailing tabs and spaces. The field separator (-F, FS) can be easily adjusted to only match spaces or also match other kind of invisible characters, provided that it's kept anchored at the end with $.

added 15 characters in body
Source Link
user313992
user313992

With perl

Here is an alternate perl solution, which is absolutely NOT an improvement upon the existing perl answer, but which, since it doesn't use the e flag of s///, could be theoretically adapted to some other tool providing a sed-like s/// and perl/pcre-like zero-width assertions in its regexes:

perl -ple 's/\s(?=\s*$)/_/g'

With perl

Here is an alternate perl solution, which is absolutely NOT an improvement upon the existing perl answer, but which, since it doesn't use the e flag of s///, could be theoretically adapted to some other tool providing a sed-like s/// and perl/pcre-like zero-width assertions in its regexes:

perl -ple 's/\s(?=\s*$)/_/g'
added 15 characters in body
Source Link
user313992
user313992
Loading
added 15 characters in body
Source Link
user313992
user313992
Loading
added 8 characters in body
Source Link
user313992
user313992
Loading
Source Link
user313992
user313992
Loading