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"....

Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
kloop
  • 4,537
  • 13
  • 42
  • 66
  • 2
    See: [How to move first column to last column in unix?](https://stackoverflow.com/q/46546890/3776858) – Cyrus Jan 15 '23 at 11:53

11 Answers11

3

With Perl

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

Output

str1 str2 ... strn [id1]
str1 str2 ... strm [id2]
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
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
Gilles Quénot
  • 173,512
  • 41
  • 224
  • 223
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]
James Brown
  • 36,089
  • 7
  • 43
  • 59
2

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

sed -E 's/([^[ ]+) (.*)/\2 [\1]/'
Fravadona
  • 13,917
  • 1
  • 23
  • 35
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
Cyrus
  • 84,225
  • 14
  • 89
  • 153
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]
Ed Morton
  • 188,023
  • 17
  • 78
  • 185
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)

Daweo
  • 31,313
  • 3
  • 12
  • 25
1

Another answer

perl -lane '$f = shift @F; push @F, "[$f]"; print "@F"' file
glenn jackman
  • 238,783
  • 38
  • 220
  • 352
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]
The fourth bird
  • 154,723
  • 16
  • 55
  • 70
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.

David C. Rankin
  • 81,885
  • 6
  • 58
  • 85
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]
RARE Kpop Manifesto
  • 2,453
  • 3
  • 11