3

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

2
  • In every line, or a specific line. If so which line? Commented Nov 28, 2018 at 21:29
  • Do some/any lines contain only a single 'word'? And/or, are some lines blank? Thx. Commented May 29, 2024 at 3:43

6 Answers 6

8

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 
2
  • @Kusalananda: Thanks. Didn't think of it. Commented Nov 28, 2018 at 21:41
  • 1
    Slightly shorter : awk '{ t=$1; $1=$NF; $NF=t}1' Commented Nov 28, 2018 at 22:37
5

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
  • 2
    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. Commented Nov 28, 2018 at 22:24
2

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.

1
  • 3
    Will not destroy the original whitespace. +1 Commented Nov 28, 2018 at 22:22
2

To swap the first and last whitespace delimited words whilst preserving whitespace around them:

perl -pe 's/(\S+)(\s.*?)(\S+)(\s*$)/$3$2$1$4/' 

Or the same with standard sed syntax:

s='[[:space:]]' S='[^[:space:]]' sed "s/\($S\{1,\}\)\($s\(.*$s\)\{0,1\}\)\($S\{1,\}\)/\4\2\1/" 

Some sed implementations have copied the \s, \S of perl though that's not standard. Some also have \+ as non-standard equivalent of \{1,\} and \? as non-standard equivalent of \{0,1\}. Some also have -r or -E to switch to extended regexps (-E will be in the next version of the standard).

With GNU sed or compatible, that can be simplified to:

sed -E 's/(\S+)(\s(.*\s)?)(\S+)/\4\2\1/' 

(replace -E with -r with older versions though -E should be preferred as that's the one that will be standard and portable going forward).

0

Here is an example of swapping first and last words of a text line using sed:

 $ echo "first second third and fourth" | sed -E 's/^([^ ]+)(.* )([^ ]+)$/\3\2\1/' fourth second third and first 

Here -E enables the use of extended regular expressions.

1
  • 1
    This would be a better answer if you documented its deficiencies/limitations and/or described the assumptions you are making.   And you really should compare and contrast it to the other sed-based answer. Commented May 25, 2024 at 17:56
0

Using Raku (formerly known as Perl_6)

Assuming whitespace delimited input and space-delimited output (like @Kusalananda's excellent Perl answer). Data is analyzed linewise and blank lines are dropped:

~$ printf "first second third and fourth\n\nhead tail\n\n" | \ raku -ne 'my @a = .words or next; put join " ", @a.tail, @a.[1..*-2], @a.head;' fourth second third and first tail head #OR: ~$ printf "first second third and fourth\n\nhead tail\n\n" | \ raku -ne 'my @a = .words or next; put join " ", @a.[*-1], @a.[1..*-2], @a.[0];' fourth second third and first tail head 

To retain blank lines, use Raku's lines routine. Note this is a better choice if you're splitting on \t tab-separated columns, if so, to preserve blank elements change .split(/\s/) to .split("\t") below:

~$ printf "first second third and fourth\nhead tail\n\n" | \ raku -e 'my @a = lines.map: *.split(/\s/); put join " ", .[*-1], .[1..*-2], .[0] for @a;' fourth second third and first tail head #OR: ~$ printf "first second third and fourth\n\nhead tail\n\n" | \ raku -e 'my @a = lines>>.split(/\s/); put join " ", .[*-1], .[1..*-2], .[0] for @a;' fourth second third and first tail head 

If you're a fan of s/// substitution the Raku answer is virtually identical to Perl, except capture numbering starts from $0:

~$ printf "first second third and fourth\n\nhead tail\n\n" | \ raku -pe 's/^ (\w*) (.*?) (\w*) $/$2$1$0/;' fourth second third and first tail head #OR: ~$ printf "first second third and fourth\n\nhead tail\n\n" | \ raku -pe 's/^ (\S*) (.*?) (\S*) $/$2$1$0/;' fourth second third and first tail head 

https://unix.stackexchange.com/a/654184/227738
https://docs.raku.org/
https://raku.org/

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.