What is a Unix oneliner to swap between fields?
Asked Answered
M

11

6

I have a file with a list

id1 str1 str2 .. strn
id2 str1 str2 .. strm

(the number of str can vary) and I want a oneliner that transforms it into

str1 str2 .. strn [id]
str1 str2 .. strm [id]

There should be a way with awk to do that, but I don't know how to take "all fields" after $1, when they are of variable length.

My idea would be something like

cat file | awk '{ print $2 and the rest " [" $1 "]" }'

but just missing the "$2 and the rest"....

Mesomorph answered 15/1, 2023 at 11:33 Comment(1)
See: How to move first column to last column in unix?Revamp
V
2
$ awk '{for(i=2;i<=NF;i++)printf "%s%s",$i,OFS (i==NF?"[" $1 "]" ORS:"")}' file

Output:

str1 str2 .. strn [id1]
str1 str2 .. strm [id2]
Vladamir answered 15/1, 2023 at 11:40 Comment(0)
M
3

With Perl

perl -F'\s+' -E 'say join " ", @F[1..$#F], "[" . @F[0] . "]"' file

Output

str1 str2 ... strn [id1]
str1 str2 ... strm [id2]
Maynord answered 15/1, 2023 at 11:58 Comment(0)
M
2

Like this:

awk '{v=$1;$1="";sub(/^ /, "");$NF=$NF" ["v"]"}1' file

Or exploded multi-line for readability:

awk '{
    v=$1
    $1=""
    sub(/^ /, "")
    $NF=$NF" ["v"]"}
    1
' file

Output

str1 str2 ... strn [id1]
str1 str2 ... strm [id2]

Explanations

code comment
v=$1 assign $1 in v variable
$1="" unset $1
sub(/^ /, "") remove leading space from $0
$NF=$NF" ["v"]" append to the latest field $NF with expected output with id as v variable
1 shorthand for print
Maynord answered 15/1, 2023 at 11:36 Comment(0)
V
2
$ awk '{for(i=2;i<=NF;i++)printf "%s%s",$i,OFS (i==NF?"[" $1 "]" ORS:"")}' file

Output:

str1 str2 .. strn [id1]
str1 str2 .. strm [id2]
Vladamir answered 15/1, 2023 at 11:40 Comment(0)
D
2

For just handling the first field then a regex based solution seems simple enough:

sed -E 's/([^[ ]+) (.*)/\2 [\1]/'
Distinguishing answered 15/1, 2023 at 11:47 Comment(0)
R
2

Everyone stands up and moves one space.

echo "a b c d e f" | awk '{ f=$1; for(i=1; i<NF; i++){ $i=$(i+1) }; $NF=f }1'

Output:

b c d e f a
Revamp answered 15/1, 2023 at 12:20 Comment(0)
S
2
$ awk '{$0=$0 " [" $1 "]"; sub(/^[^ ]+ /,"")} 1' file
str1 str2 .. strn [id1]
str1 str2 .. strm [id2]

or if you prefer:

$ awk '{for (i=2; i<=NF; i++) printf "%s ", $i; print "[" $1 "]"}' file
str1 str2 .. strn [id1]
str1 str2 .. strm [id2]
Sneaking answered 15/1, 2023 at 13:54 Comment(0)
A
1

I would harness GNU AWK following way, let file.txt content be

id1 str1 str2 .. strn
id2 str1 str2 .. strm

then

awk '{print substr($0,index($0," ")+1),"[" $1 "]"}' file.txt

gives output

str1 str2 .. strn [id1]
str1 str2 .. strm [id2]

Warning: I assume that your values are sheared by single spaces, if this is not case do not use this solution. Explanation: I use String functions to get

$2 and the rest

by finding placement of first space (via index function) and than getting everything beyond that space (via substr function), which is then followed by 1st field value encased in [...].

(tested in GNU Awk 5.0.1)

Atli answered 15/1, 2023 at 12:8 Comment(0)
L
1

Another answer

perl -lane '$f = shift @F; push @F, "[$f]"; print "@F"' file
Lumisterol answered 15/1, 2023 at 20:48 Comment(0)
S
0

Using gnu awk and a pattern with 2 capture groups:

awk 'match($0, /^([^ ]+) +(.+)/, a) {print a[2], "["a[1]"]"}' file

Or using POSIX bracket expressions to match spaces with [[:space:]]

gawk 'match($0, /^([^[:space:]]+)[[:space:]]+(.+)/, a) {print a[2], "["a[1]"]"}' file

Output

str1 str2 .. strn [id1]
str1 str2 .. strm [id2]
Snowball answered 15/1, 2023 at 17:46 Comment(0)
L
0

Or a very close, but whitespace agnostic substitution with sed using the [[:space:]] list in character-class to handle the whitespace regardless of whether it is ' ', or '\t' or mixed using sed with the extended REGEX option would be:

sed -E 's/^([^[:space:]]+)[[:space:]]+(.*$)/\2 [\1]/' file

If your sed doesn't support extended regex (and doesn't use the -r option instead of -E for it), then you can do it with standard set regex by escaping the '(' and ')' backreference capture delimiters, e.g.

sed 's/^\([^[:space:]][^[:space:]]*\)[[:space:]][[:space:]]*\(.*$\)/\2 [\1]/' file

(note: standard regex doesn't support the '+', one-or-more occurrence, repetition operator so the '*' zero-or-more repetition is used requiring one literal match also be included to create the equivalent one-or-more repetition match.)

In both cases the standard substitution form is 's/find/replace/ shown with Extended REGEX below where:

find:

  • ^ anchors the search at the beginning of line,
  • ([^[:space:]]+) captures all (one-or-more) characters from the beginning that are not whitespace (the '^' in the character-class inverts the match) for use as the 1st backreference,
  • [[:space:]]+ select one or more spaces,
  • (.*$) capture all remaining characters to '$' (end of line) for the 2nd backreference.

replace

  • \2 insert the text captured as the 2nd backreference,
  • ' ' insert a space, and finally,
  • \1 insert the text captured as the 1st backreference.

Example Use/Output

$ sed -E 's/^([^[:space:]]+)[[:space:]]+(.*$)/\2 [\1]/' << 'eof'
id1 str1 str2 .. strn
id2 str1 str2 .. strm
eof
str1 str2 .. strn [id1]
str1 str2 .. strm [id2]

Let me know if you have questions.

Livvie answered 15/1, 2023 at 23:1 Comment(0)
X
0

this should be one-liner- enough ?

echo 'id1 str1 str2 .. strn
      id2 str1 str2 .. strm' | 
{m,g}awk '$!NF = substr($_, index($_, $(!_+!_))) " [" $!_ "]"'
str1 str2 .. strn [id1]
str1 str2 .. strm [id2]
Xanthous answered 16/1, 2023 at 21:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.