Add space within a line - shell

I have many files named a, b, c and so on. These files contain line like this:-
11.077-105.882
-22.134-302.321
-1.011-201.254
I want to add a space when - sign come in mid of line. I want my output file look like this:-
11.077 -105.882
-22.134 -302.321
-1.011 -201.254
I have tried this command:-
cat a |sed 's/-/ -/g' >out.txt
But it do not give desired result

Require (and capture) a character before each - to replace:
$ sed 's/\(.\)-/\1 -/g' < tmp.txt
11.077 -105.882
-22.134 -302.321
-1.011 -201.254
This will only match a - that is not line-initial, and will include the preceding character in the replacement text.

You could combine 2 sed commands:
$ sed 's/-/ -/g' a | sed 's/^ //'
11.077 -105.882
-22.134 -302.321
-1.011 -201.254
Or, in a single line solution add whitespaces only before - that come after a digit:
$ sed 's,\([0-9]\)-,\1 -,' a
11.077 -105.882
-22.134 -302.321
-1.011 -201.254

Related

How to get values in a line while looping line by line in a file (shell script)

I have a file which looks like this (file.txt)
{"key":"AJGUIGIDH568","rule":squid:111-some_random_text_here
{"key":"TJHJHJHDH568","rule":squid:111-some_random_text_here
{"key":"YUUUIGIDH566","rule":squid:111-some_random_text_here
{"key":"HJHHIGIDH568","rule":squid:111-some_random_text_here
{"key":"ATYUGUIDH556","rule":squid:111-some_random_text_here
{"key":"QfgUIGIDH568","rule":squid:111-some_random_text_here
I want to loop trough this line by line an extract the key values.
so the result should be like ,
AJGUIGIDH568
AJGUIGIDH568
YUUUIGIDH566
HJHHIGIDH568
ATYUGUIDH556
QfgUIGIDH568
So I wrote a code like this to loop line by line and extract the value between {"key":" and ","rule": because key values is in between these 2 patterns.
while read p; do
echo $p | sed -n "/{"key":"/,/","rule":,/p"
done < file.txt
But this is not working. can someone help me to figure out me this. Thanks in advance.
Your sample input is almost valid json. You could tweak it to make it valid and then extract the values with jq with something like:
sed -e 's/squid/"squid/' -e 's/$/"}/' file.txt | jq -r .key
Or, if your actual input really is valid json, then just use jq:
jq -r .key file.txt
If the "random-txt" may include double quotes, making it difficult to massage the input to make it valid json, perhaps you want something like:
awk '{print $4}' FS='"' file.txt
or
sed -n '/{"key":"\([^"]*\).*/s//\1/p' file.txt
or
while IFS=\" read open_brace key colon val _; do echo "$val"; done < file.txt
For the shown data, you can try this awk:
awk -F '"[:,]"' '{print $2}' file
AJGUIGIDH568
TJHJHJHDH568
YUUUIGIDH566
HJHHIGIDH568
ATYUGUIDH556
QfgUIGIDH568
With the give example you can simple use
cut -d'"' -f4 file.txt
Assumptions:
there may be other lines in the file so we need to focus on just the lines with "key" and "rule"
the only text between "key" and "rule" is the desired string (eg, squid never shows up between the two patterns of interest)
Adding some additional lines:
$ cat file.txt
{"key":"AJGUIGIDH568","rule":squid:111-some_random_text_here
ignore this line}
{"key":"TJHJHJHDH568","rule":squid:111-some_random_text_here
ignore this line}
{"key":"YUUUIGIDH566","rule":squid:111-some_random_text_here
ignore this line}
{"key":"HJHHIGIDH568","rule":squid:111-some_random_text_here
ignore this line}
{"key":"ATYUGUIDH556","rule":squid:111-some_random_text_here
ignore this line}
{"key":"QfgUIGIDH568","rule":squid:111-some_random_text_here
ignore this line}
One sed idea:
$ sed -nE 's/^(.*"key":")([^"]*)(","rule".*)$/\2/p' file.txt
AJGUIGIDH568
TJHJHJHDH568
YUUUIGIDH566
HJHHIGIDH568
ATYUGUIDH556
QfgUIGIDH568
Where:
-E - enable extended regex support (and capture groups without need to escape sequences)
-n - suppress printing of pattern space
^(.*"key":") - [1st capture group] everything from start of line up to and including "key":"
([^"]*) - [2nd capture group] everything that is not a double quote (")
(","rule".*)$ - [3rd capture group] everything from ",rule" to end of line
\2/p - replace the line with the contents of the 2nd capture group and print

Transform multiple files in a directory in unix

I have a folder with the name Translated_cds.
in this folder, there are 52 text files. these are FASTA files that have information about proteins.
>lcl|NZ_JPMI01000003.1_prot_WP_043388330.1_1 [locus_tag=Q664_RS00010] [protein=HAMP domain-containing protein] [protein_id=WP_043388330.1] [location=complement(30..1904)] [gbkey=CDS]
MRIRTRLLLLLIVTAAVPTLAVGLLAWRDAERALSEAVAEQHRRTALAEAEHAATHVLSLATELGGALVHQEPLELGPSE
AQEFLIRVFLRRDRIAQVGLFDARGQLTASVFVDDPEAFARQEPQFRRHDTVAAGEVEDFQRRASELLSQVPEGRAYAIS
APYLTGVRRRPAVVVAARAPGTRTGGLAAELGLEELSQRLAARGVGDERVFLLDGAGRLLLDGEPERERHEDFTGKLPGA
VGARQTGLAAYEEEGRAWLAAYSPVPELGWVAVVARPREAALAPLHALARSTYGVLGLTLLGVLALALMLARALARPIAR
LAEGARALARGNLAHRISLKRRDELGDLARAFNDMGQALEQAHRELLGFNEQLAAQVEERTRELQQTQVQLSRSQRLAAM
GDLAAGMAHEMNNPLAAVLGNVQLMLMDLPKEDPSHRMLGTVHQQAQRIASIVRELQLLSERQQLGRLPLDLHRMLQRVL
ESRCAELSQVGVHVDCRFHPGEVKVLGDTQALGDVLGRLLGNALNAMRDRPERNLVLSTQVVDAEVVRVEMKDTGRGIAR
EHLERIFNPFFTTKQQWTGKGLSLAVCHRVIEDHGGTITLDSVEGVGTTVTLVLPAAPASSGLV
the line starting with > (called the header)is present in all the files. I want to replace the gap ' ' in the headers with _.
till now i have tried this
sed -i 's/ /_/g' Translated_cds*
We can lead with /^>/ to gate the substitution so that it isolates to the pattern we are interested in:
sed -i -e '/^>/ s/ /_/g' Translated_cds*
My test:
echo '>lcl|NZ_JPMI01000003.1_prot_WP_043388330.1_1 [locus_tag=Q664_RS00010] [protein=HAMP domain-containing protein] [protein_id=WP_043388330.1] [location=complement(30..1904)] [gbkey=CDS]
MRIRTRLLLLLIVTAAVPTLAVGLLAWRDAERALSEAVAEQHRRTALAEAEHAATHVLSLATELGGALVHQEPLELGPSE
AQEFLIRVFLRRDRIAQVGLFDARGQLTASVFVDDPEAFARQEPQFRRHDTVAAGEVEDFQRRASELLSQVPEGRAYAIS
APYLTGVRRRPAVVVAARAPGTRTGGLAAELGLEELSQRLAARGVGDERVFLLDGAGRLLLDGEPERERHEDFTGKLPGA
VGARQTGLAAYEEEGRAWLAAYSPVPELGWVAVVARPREAALAPLHALARSTYGVLGLTLLGVLALALMLARALARPIAR
LAEGARALARGNLAHRISLKRRDELGDLARAFNDMGQALEQAHRELLGFNEQLAAQVEERTRELQQTQVQLSRSQRLAAM
GDLAAGMAHEMNNPLAAVLGNVQLMLMDLPKEDPSHRMLGTVHQQAQRIASIVRELQLLSERQQLGRLPLDLHRMLQRVL
ESRCAELSQVGVHVDCRFHPGEVKVLGDTQALGDVLGRLLGNALNAMRDRPERNLVLSTQVVDAEVVRVEMKDTGRGIAR
EHLERIFNPFFTTKQQWTGKGLSLAVCHRVIEDHGGTITLDSVEGVGTTVTLVLPAAPASSGLV' | sed -e '/^>/ s/ /_/g'
My result:
>lcl|NZ_JPMI01000003.1_prot_WP_043388330.1_1_[locus_tag=Q664_RS00010]_[protein=HAMP_domain-containing_protein]_[protein_id=WP_043388330.1]_[location=complement(30..1904)]_[gbkey=CDS]
MRIRTRLLLLLIVTAAVPTLAVGLLAWRDAERALSEAVAEQHRRTALAEAEHAATHVLSLATELGGALVHQEPLELGPSE
AQEFLIRVFLRRDRIAQVGLFDARGQLTASVFVDDPEAFARQEPQFRRHDTVAAGEVEDFQRRASELLSQVPEGRAYAIS
APYLTGVRRRPAVVVAARAPGTRTGGLAAELGLEELSQRLAARGVGDERVFLLDGAGRLLLDGEPERERHEDFTGKLPGA
VGARQTGLAAYEEEGRAWLAAYSPVPELGWVAVVARPREAALAPLHALARSTYGVLGLTLLGVLALALMLARALARPIAR
LAEGARALARGNLAHRISLKRRDELGDLARAFNDMGQALEQAHRELLGFNEQLAAQVEERTRELQQTQVQLSRSQRLAAM
GDLAAGMAHEMNNPLAAVLGNVQLMLMDLPKEDPSHRMLGTVHQQAQRIASIVRELQLLSERQQLGRLPLDLHRMLQRVL
ESRCAELSQVGVHVDCRFHPGEVKVLGDTQALGDVLGRLLGNALNAMRDRPERNLVLSTQVVDAEVVRVEMKDTGRGIAR
EHLERIFNPFFTTKQQWTGKGLSLAVCHRVIEDHGGTITLDSVEGVGTTVTLVLPAAPASSGLV
If we want only the spaces within the keyword/value tags of the header replaced, then:
sed -i -e '/^>/ s/\([A-Za-z0-9]\) \([[A-Za-z0-9]\)/\1_\2/g' Translated_cds*
Or.... We can clarify a bit with more modern regex:
sed -i -E '/^>/ s/([[:alnum:]]) ([[:alnum:]])/\1_\2/g' Translated_cds*
The result will change only inside the header's keyword/value tags:
>lcl|NZ_JPMI01000003.1_prot_WP_043388330.1_1 [locus_tag=Q664_RS00010] [protein=HAMP_domain-containing_protein] [protein_id=WP_043388330.1] [location=complement(30..1904)] [gbkey=CDS]
MRIRTRLLLLLIVTAAVPTLAVGLLAWRDAERALSEAVAEQHRRTALAEAEHAATHVLSLATELGGALVHQEPLELGPSE
AQEFLIRVFLRRDRIAQVGLFDARGQLTASVFVDDPEAFARQEPQFRRHDTVAAGEVEDFQRRASELLSQVPEGRAYAIS
APYLTGVRRRPAVVVAARAPGTRTGGLAAELGLEELSQRLAARGVGDERVFLLDGAGRLLLDGEPERERHEDFTGKLPGA
VGARQTGLAAYEEEGRAWLAAYSPVPELGWVAVVARPREAALAPLHALARSTYGVLGLTLLGVLALALMLARALARPIAR
LAEGARALARGNLAHRISLKRRDELGDLARAFNDMGQALEQAHRELLGFNEQLAAQVEERTRELQQTQVQLSRSQRLAAM
GDLAAGMAHEMNNPLAAVLGNVQLMLMDLPKEDPSHRMLGTVHQQAQRIASIVRELQLLSERQQLGRLPLDLHRMLQRVL
ESRCAELSQVGVHVDCRFHPGEVKVLGDTQALGDVLGRLLGNALNAMRDRPERNLVLSTQVVDAEVVRVEMKDTGRGIAR
EHLERIFNPFFTTKQQWTGKGLSLAVCHRVIEDHGGTITLDSVEGVGTTVTLVLPAAPASSGLV

String manipulation via script

I am trying to get a substring between &DEST= and the next & or a line break.
For example :
MYREQUESTISTO8764GETTHIS&DEST=SFO&ORIG=6546
In this I need to extract "SFO"
MYREQUESTISTO8764GETTHIS&DEST=SANFRANSISCO&ORIG=6546
In this I need to extract "SANFRANSISCO"
MYREQUESTISTO8764GETTHISWITH&DEST=SANJOSE
In this I need to extract "SANJOSE"
I am reading a file line by line, and I need to update the text after &DEST= and put it back in the file. The modification of the text is to mask the dest value with X character.
So, SFO should be replaced with XXX.
SANJOSE should be replaced with XXXXXXX.
Output :
MYREQUESTISTO8764GETTHIS&DEST=XXX&ORIG=6546
MYREQUESTISTO8764GETTHIS&DEST=XXXXXXXXXXXX&ORIG=6546
MYREQUESTISTO8764GETTHISWITH&DEST=XXXXXXX
Please let me know how to achieve this in script (Preferably shell or bash script).
Thanks.
$ cat file
MYREQUESTISTO8764GETTHIS&DEST=SFO&ORIG=6546
MYREQUESTISTO8764GETTHIS&DEST=PORTORICA
MYREQUESTISTO8764GETTHIS&DEST=SANFRANSISCO&ORIG=6546
MYREQUESTISTO8764GETTHISWITH&DEST=SANJOSE
$ sed -E 's/^.*&DEST=([^&]*)[&]*.*$/\1/' file
SFO
PORTORICA
SANFRANSISCO
SANJOSE
should do it
Replacing airports with an equal number of Xs
Let's consider this test file:
$ cat file
MYREQUESTISTO8764GETTHIS&DEST=SFO&ORIG=6546
MYREQUESTISTO8764GETTHIS&DEST=SANFRANSISCO&ORIG=6546
MYREQUESTISTO8764GETTHISWITH&DEST=SANJOSE
To replace the strings after &DEST= with an equal length of X and using GNU sed:
$ sed -E ':a; s/(&DEST=X*)[^X&]/\1X/; ta' file
MYREQUESTISTO8764GETTHIS&DEST=XXX&ORIG=6546
MYREQUESTISTO8764GETTHIS&DEST=XXXXXXXXXXXX&ORIG=6546
MYREQUESTISTO8764GETTHISWITH&DEST=XXXXXXX
To replace the file in-place:
sed -i -E ':a; s/(&DEST=X*)[^X&]/\1X/; ta' file
The above was tested with GNU sed. For BSD (OSX) sed, try:
sed -Ee :a -e 's/(&DEST=X*)[^X&]/\1X/' -e ta file
Or, to change in-place with BSD(OSX) sed, try:
sed -i '' -Ee :a -e 's/(&DEST=X*)[^X&]/\1X/' -e ta file
If there is some reason why it is important to use the shell to read the file line-by-line:
while IFS= read -r line
do
echo "$line" | sed -Ee :a -e 's/(&DEST=X*)[^X&]/\1X/' -e ta
done <file
How it works
Let's consider this code:
search_str="&DEST="
newfile=chart.txt
sed -E ':a; s/('"$search_str"'X*)[^X&]/\1X/; ta' "$newfile"
-E
This tells sed to use Extended Regular Expressions (ERE). This has the advantage of requiring fewer backslashes to escape things.
:a
This creates a label a.
s/('"$search_str"'X*)[^X&]/\1X/
This looks for $search_str followed by any number of X followed by any character that is not X or &. Because of the parens, everything except that last character is saved into group 1. This string is replaced by group 1, denoted \1 and an X.
ta
In sed, t is a test command. If the substitution was made (meaning that some character needed to be replaced by X), then the test evaluates to true and, in that case, ta tells sed to jump to label a.
This test-and-jump causes the substitution to be repeated as many times as necessary.
Replacing multiple tags with one sed command
$ name='DEST|ORIG'; sed -E ':a; s/(&('"$name"')=X*)[^X&]/\1X/; ta' file
MYREQUESTISTO8764GETTHIS&DEST=XXX&ORIG=XXXX
MYREQUESTISTO8764GETTHIS&DEST=XXXXXXXXXXXX&ORIG=XXXX
MYREQUESTISTO8764GETTHISWITH&DEST=XXXXXXX
Answer for original question
Using shell
$ s='MYREQUESTISTO8764GETTHIS&DEST=SFO&ORIG=6546'
$ s=${s#*&DEST=}
$ echo ${s%%&*}
SFO
How it works:
${s#*&DEST=} is prefix removal. This removes all text up to and including the first occurrence of &DEST=.
${s%%&*} is suffix removal_. It removes all text from the first & to the end of the string.
Using awk
$ echo 'MYREQUESTISTO8764GETTHIS&DEST=SFO&ORIG=6546' | awk -F'[=\n]' '$1=="DEST"{print $2}' RS='&'
SFO
How it works:
-F'[=\n]'
This tells awk to treat either an equal sign or a newline as the field separator
$1=="DEST"{print $2}
If the first field is DEST, then print the second field.
RS='&'
This sets the record separator to &.
With GNU bash:
while IFS= read -r line; do
[[ $line =~ (.*&DEST=)(.*)((&.*|$)) ]] && echo "${BASH_REMATCH[1]}fooooo${BASH_REMATCH[3]}"
done < file
Output:
MYREQUESTISTO8764GETTHIS&DEST=fooooo&ORIG=6546
MYREQUESTISTO8764GETTHIS&DEST=fooooo&ORIG=6546
MYREQUESTISTO8764GETTHISWITH&DEST=fooooo
Replace the characters between &DEST and & (or EOL) with x's:
awk -F'&DEST=' '{
printf("%s&DEST=", $1);
xlen=index($2,"&");
if ( xlen == 0) xlen=length($2)+1;
for (i=0;i<xlen;i++) printf("%s", "X");
endstr=substr($2,xlen);
printf("%s\n", endstr);
}' file

Bash command to extract characters in a string

I want to write a small script to generate the location of a file in an NGINX cache directory.
The format of the path is:
/path/to/nginx/cache/d8/40/32/13febd65d65112badd0aa90a15d84032
Note the last 6 characters: d8 40 32, are represented in the path.
As an input I give the md5 hash (13febd65d65112badd0aa90a15d84032) and I want to generate the output: d8/40/32/13febd65d65112badd0aa90a15d84032
I'm sure sed or awk will be handy, but I don't know yet how...
This awk can make it:
awk 'BEGIN{FS=""; OFS="/"}{print $(NF-5)$(NF-4), $(NF-3)$(NF-2), $(NF-1)$NF, $0}'
Explanation
BEGIN{FS=""; OFS="/"}. FS="" sets the input field separator to be "", so that every char will be a different field. OFS="/" sets the output field separator as /, for print matters.
print ... $(NF-1)$NF, $0 prints the penultimate field and the last one all together; then, the whole string. The comma is "filled" with the OFS, which is /.
Test
$ awk 'BEGIN{FS=""; OFS="/"}{print $(NF-5)$(NF-4), $(NF-3)$(NF-2), $(NF-1)$NF, $0}' <<< "13febd65d65112badd0aa90a15d84032"
d8/40/32/13febd65d65112badd0aa90a15d84032
Or with a file:
$ cat a
13febd65d65112badd0aa90a15d84032
13febd65d65112badd0aa90a15f1f2f3
$ awk 'BEGIN{FS=""; OFS="/"}{print $(NF-5)$(NF-4), $(NF-3)$(NF-2), $(NF-1)$NF, $0}' a
d8/40/32/13febd65d65112badd0aa90a15d84032
f1/f2/f3/13febd65d65112badd0aa90a15f1f2f3
With sed:
echo '13febd65d65112badd0aa90a15d84032' | \
sed -n 's/\(.*\([0-9a-f]\{2\}\)\([0-9a-f]\{2\}\)\([0-9a-f]\{2\}\)\)$/\2\/\3\/\4\/\1/p;'
Having GNU sed you can even simplify the pattern using the -r option. Now you won't need to escape {} and () any more. Using ~ as the regex delimiter allows to use the path separator / without need to escape it:
sed -nr 's~(.*([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2}))$~\2/\3/\4/\1~p;'
Output:
d8/40/32/13febd65d65112badd0aa90a15d84032
Explained simple the pattern does the following: It matches:
(all (n-5 - n-4) (n-3 - n-2) (n-1 - n-0))
and replaces it by
/$1/$2/$3/$0
You can use a regular expression to separate each of the last 3 bytes from the rest of the hash.
hash=13febd65d65112badd0aa90a15d84032
[[ $hash =~ (..)(..)(..)$ ]]
new_path="/path/to/nginx/cache/${BASH_REMATCH[1]}/${BASH_REMATCH[2]}/${BASH_REMATCH[3]}/$hash"
Base="/path/to/nginx/cache/"
echo '13febd65d65112badd0aa90a15d84032' | \
sed "s|\(.*\(..\)\(..\)\(..\)\)|${Base}\2/\3/\4/\1|"
# or
# sed sed 's|.*\(..\)\(..\)\(..\)$|${Base}\1/\2/\3/&|'
Assuming info is a correct MD5 (and only) string
First of all - thanks to all of the responders - this was extremely quick!
I also did my own scripting meantime, and came up with this solution:
Run this script with a parameter of the URL you're looking for (www.example.com/article/76232?q=hello for example)
#!/bin/bash
path=$1
md5=$(echo -n "$path" | md5sum | cut -f1 -d' ')
p3=$(echo "${md5:0-2:2}")
p2=$(echo "${md5:0-4:2}")
p1=$(echo "${md5:0-6:2}")
echo "/path/to/nginx/cache/$p1/$p2/$p3/$md5"
This assumes the NGINX cache has a key structure of 2:2:2.

sed right align a group of text

this question originated from string pattaren-matching using awk , basically we are splitting a line of text in multiple groups based on a regex pattern, and then printing two groups only. Now the question is can we right align a group while printing through sed?
below is an example
$cat input.txt
it is line one
it is longggggggg one
itttttttttt is another one
now
$sed -e 's/\(.*\) \(.*\) \(.*\) \(.*\)/\1 \3/g' input.txt
it splits and prints group 1 and 3, but the output is
it line
it longggggggg
itttttttttt another
my question is can we do it through sed so that the output comes as
it line
it longggggggg
itttttttttt another
I did it with awk but I feel it can be done through sed, but I am not able to get how I am going to get the length of the second group and then pad correct number of spaces in between the groups, I am open to any suggestions to try out.
This might work for you (GNU sed):
sed -r 's/^(.*) .* (.*) .*$/\1 \2/;:a;s/^.{1,40}$/ &/;ta;s/^( *)(\S*)/\2\1/' file
or:
sed -r 's/^(.*) .* (.*) .*$/printf "%-20s%20s" \1 \2/e' file
You can use looping in sed to achieve what you want:
#!/bin/bash
echo 'aa bb cc dd
11 22 33333333 44
ONE TWO THREEEEEEEEE FOUR' | \
sed -e 's/\(.*\) \(.*\) \(.*\) \(.*\)/\1 \3/g' \
-e '/\([^ ]*\) \([^ ]*\)/ { :x ; s/^\(.\{1,19\}\) \(.\{1,19\}\)$/\1 \2/g ; tx }'
The two 19's control the width of your columns. The :x is a label which is looped to by tx whenever the preceding substitution succeeded. (You could add a p; before tx to "debug" it.
It most easy to use awk in this case...
You could too use a bash loop to calculate the number of space and run this command on the line covered :
while read; do
# ... calculate $SPACE ...
echo $REPLY|sed "s/\([^\ ]*\)\ *[^\ ]*\ *\([^\ ]*\)/\1$SPACES\2/g"
done < file
But I prefer use awk for do all that (or other advanced shell languages ​​such as Perl, Python, PHP shell mode, ...)
TemplateSpace=" "
TemplateSize=${#TemplateSpace}
sed "
# split your group (based on word here but depend on your real need)
s/^ *\(\w\) \(\w\) \(\w\) \(\w\).*$/\1 \3/
# align
s/$/${TemplateSpace}/
s/^\(.\{${TemplateSize}\}\).*$/\1/
s/\(\w\) \(\w\)\( *\)/\1 \3\2/
"
or more simple for avoiding TemplateSize (and there are no dot in content)
TemplateSpace="............................................................."
and replace
s/^\(.\{${TemplateSize}\}.*$/\1/
by
s/^\(${TemplateSpace}\).*$/\1/
s/\./ /g
Del columns 2 and 4. Right justify resulting col 2 at line length of 23 chars.
sed -e '
s/[^ ]\+/ /4;
s/[^ ]\+//2;
s/^\(.\{23\}\).*$/\1/;
s/\(^[^ ]\+[ ]\+\)\([^ ]\+\)\([ ]\+\)/\1\3\2/;
'
or gnu sed with extended regex:
sed -r '
s/\W+\w+\W+(\w+)\W+\w+$/\1 /;
s/^(.{23}).*/\1/;
s/(+\W)(\w+)(\W+)$/\1\3\2/
'
This question is old, but I like to see it as a puzzle.
While I love the loop solution for its brevity, here is one without a loop or shell help.
sed -E "s/ \w+ (\w+) \w+$/ \1/;h;s/./ /g;s/$/# /;s/( *)#\1//;x;H;x;s/\n//;s/^( *)(\w+)/\2\1/"
or without extended regex
sed "s/ .* \(.*\) .*$/ \1/;h;s/./ /g;s/$/# /;s/\( *\)#\1//;x;H;x;s/\n//;s/^\( *\)\([^ ]*\)/\2\1/"

Resources