0

I was trying to replace the first word in a line with the last word in a line using a sed command i=but i can't seem to figure it out.

  • In every line, or a specific line. If so which line? – Jesse_b Nov 28 '18 at 21:29
5

Using awk:

awk '{ t=$1; $1=$NF; $NF=t; print}'

This will:

  • t=$1 - set t to the first word
  • $1=$NF - set the first word to the last word
  • $NF=t - set the last word to the first word
  • print - print the new line.

$ echo 'one two three four five six' | awk '{ f=$1; l=$NF; $1=l; $NF=f; print}'
six two three four five one
  • @Kusalananda: Thanks. Didn't think of it. – Jesse_b Nov 28 '18 at 21:41
  • 1
    Slightly shorter : awk '{ t=$1; $1=$NF; $NF=t}1' – steve Nov 28 '18 at 22:37
4

Using Perl, and assuming whitespace delimited input and space-delimited output:

perl -ape '($F[0],$F[-1])=($F[-1],$F[0]);$_="@F\n"'

Testure:

$ printf 'Cleanse Fold and Manipulate\n' | perl -ape '($F[0],$F[-1])=($F[-1],$F[0]);$_="@F\n"'
Manipulate Fold and Cleanse

The Perl code, using -a to split the input on whitespace into the array @F, simply swaps the two elements at the start and end of that array before joining the resulting list with spaces, adding a newline at the end.

A shorter Perl variant that matches the first and last words and swaps them in a substitution (this assumes that there are no flanking whitespace in the input though):

perl -pe 's/^(\w*)(.*?)(\w*)$/$3$2$1/'

The middle bit, .*?, matches the middle of the string non-greedily. We couldn't have done this this easily with sed as there is no non-greedy modifier like that ? after .*.

  • 1
    Or, allowing for leading trailing whitespace: perl -pe 's/^(\s*)(\S+)(.+?)(\S+)(\s*)$/$1$4$3$2$5/' -- note the middle bit has at least 1 char to account for separate first/last words. – glenn jackman Nov 28 '18 at 22:24
1

Semi-serious answer: it's not you, you are fine. The problem is totally in sed's s/// command's verbosity (compare this with the alternative answers):

$ echo "Hello some good world!" |
sed 's/\(^[^[:space:]]\+\)\([[:space:]].*[[:space:]]\|[[:space:]]\+\)\([^[:space:]]\+$\)/\3\2\1/'
world! some good Hello

We may also want to swap the first and the last words even if we have space characters before the first and/or after the latter (thanks to comments and other answers):

$ echo "  Hello some   good world!  " |
sed 's/^\([[:space:]]*\)\([^[:space:]]\+\)\([[:space:]].*[[:space:]]\|[[:space:]]\+\)\([^[:space:]]\+\)\([[:space:]]*\)$/\1\4\3\2\5/'
  world! some   good Hello  

However, these commands use some non-POSIX GNU extensions to the BRE - Basic Regular Expression - syntax (namely, + and |).
A (more portable) command that satisfies the POSIX standard while keeping the convenience of alternation (|) would require Extended regular expressions. For example, using GNU sed with the --posix option (which disables GNU extensions - not actually required):

$ echo "  Hello some   good world!  " |
sed --posix -E 's/^([[:space:]]*)([^[:space:]]{1,})([[:space:]].*[[:space:]]|[[:space:]]{1,})([^[:space:]]{1,})([[:space:]]*)$/\1\4\3\2\5/'
  world! some   good Hello  

Note, however, that POSIX ERE syntax does not include backreferences. This command, too, will require an implementation with extensions to succeed.

  • 3
    Will not destroy the original whitespace. +1 – glenn jackman Nov 28 '18 at 22:22

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.