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.
- In every line, or a specific line. If so which line?jesse_b– jesse_b2018-11-28 21:29:34 +00:00Commented Nov 28, 2018 at 21:29
- Do some/any lines contain only a single 'word'? And/or, are some lines blank? Thx.jubilatious1– jubilatious12024-05-29 03:43:59 +00:00Commented May 29, 2024 at 3:43
6 Answers
Using awk:
awk '{ t=$1; $1=$NF; $NF=t; print}' This will:
t=$1- settto the first word$1=$NF- set the first word to the last word$NF=t- set the last word to the first wordprint- 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 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 .*.
- 2Or, 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– glenn jackman2018-11-28 22:24:23 +00:00Commented Nov 28, 2018 at 22:24
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.
- 3Will not destroy the original whitespace. +1glenn jackman– glenn jackman2018-11-28 22:22:31 +00:00Commented Nov 28, 2018 at 22:22
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).
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.
- 1This 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.G-Man Says 'Reinstate Monica'– G-Man Says 'Reinstate Monica'2024-05-25 17:56:06 +00:00Commented May 25, 2024 at 17:56
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/