0

In osx shell, given a stream with these lines, where the first token is a key, the first character on the line will always match [a-z], the remaining tokens will always be only numeric, there are a variable number of them, and only single spaces separate the tokens:

key  [1] [...] [n]
---- --------------------
key1 17 89 52
key2 5 189 6 3 5 21

How can I emit the following output (independent of the number of tokens on each line) without just doing replaces over and over?

17 key1
89 key1
52 key1
5 key2
189 key2
6 key2
3 key2
5 key2
21 key2

(It's also perfectly fine for key and number to be swapped, or for each line to remain one line separated by spaces instead of newlines like key1 17 key1 89 key1 52, as I can swap tokens or split them into multiple lines easily).

I am currently using sed to successively replace each next keyless number, but this seems inefficient, and I have to ensure I pipe to sed more times than the maximum number of tokens, which could increase (and did, why else would you guess I'm here?):

sed -E 's/^([a-z][^ ]*) ([0-9]+) /\2 \1\n\1 /g' filename.txt | sed ... | sed ...

If I could afford the time to dig into awk I am sure that would work. Maybe cut can do the job or one of the other tools that can effectively work with tokens.

How would you do this efficiently in both code and processing time?

2 Answers 2

2

You can use awk for this purpose

awk '{ for(i = 2; i <= NF; i++) { print $i,$1; } }'  file

The for loop goes from the second field to the last, and each field is printed with the first field appended

1
  • I knew it would be easy!!!!! Thank you. one second while I test it out... er, that is, 9 minutes while I wait to be allowed to accept your answer.
    – ErikE
    Jul 18, 2018 at 4:21
1

sed is eminently suited for this task. Just tweaking your sed code a little, we have:

sed -E '
    s/^([a-z][^ ]*) ([0-9]+)/\2 \1\n\1/
    /\n/P;D
' filename.txt

Output:

17 key1
89 key1
52 key1
5 key2
189 key2
6 key2
3 key2
5 key2
21 key2

Explanation:

  • You already know the substitute s/// command which I took from you and just remove the global /g flag.
  • Basic idea is that we look at the first two elements, flip them, and also save a copy of the first element (before flip) and place a newline \n after the flipping operation, so that we may be able to use the P command, which prints just upto the first newline in the pattern space.
  • Qualify the P with a /\n/ so that an infinite loop is avoided.
  • The D deletes upto the first newline in the pattern space, and with what remains of the pattern space, takes the control back to the top of the script. IOW, what you have done is , provided for an implicit looping mechanism.
  • The looping ends, for the current line, when the pattern space has finally been eaten away by this continuous process of s/// --- P --- D --- s/// --- P --- D ............
  • After that, sed starts a new reading cycle and then you already know what happens .... HTH.
2
  • Could you explain how this works?
    – ErikE
    Jul 18, 2018 at 8:25
  • @ErikE Added a brief explanatory section. You may check. Jul 18, 2018 at 10:43

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .