5

This question already has an answer here:

An example of my file looks like this:

201012,720,201011,119,201710,16

Output I want:

201012,720
201011,119
201710,16

marked as duplicate by Sundeep, Anthony Geoghegan, Philippos, msp9011, Rui F Ribeiro May 20 at 10:44

This question has been asked before and already has an answer. If those answers do not fully address your question, please ask a new question.

  • Please specify whether or not the lines of the file always have an even number of fields. – agc May 18 at 18:19
5

Using a Sed loop:

sed -e 's/,/\n/2' -e 'P;D' file

Ex.

$ echo '201012,720,201011,119,201710,16' | sed -e 's/,/\n/2' -e 'P;D'
201012,720
201011,119
201710,16

This replaces the second , with \n, then prints and deletes up the \n, repeatedly until the substitution is no longer successful.

BSD doesn't understand newline as \n in right side of s commands, this is a workaround for ksh,bash,zsh shells:

sed -e :a -e $'s/,/\\\n/2' -e 'P;D' file

Or, a general solution for (old) seds:

sed '
:a
s/,/\
/2
P;D
' file
  • Thanks a lot.. :) – Tejendra Khatri May 18 at 14:31
  • Able to process mutiline files even if fields on a line are odd (not-even): +1 – Isaac May 18 at 15:42
  • Doesn't work with BSD sed – Jesse_b May 18 at 15:47
  • @Isaac: It just prints it all on one line like: 201012,720n201011,119,201710,16 – Jesse_b May 18 at 17:38
  • @Jesse_b Ah, when is bsd going to accept \n as a new line? sigh ... – Isaac May 18 at 17:44
5
$ paste -d, - - < <( tr ',' '\n' <file )
201012,720
201011,119
201710,16

or, without the process substitution,

$ tr ',' '\n' <file | paste -d, - -
201012,720
201011,119
201710,16

This replaces all commas in the file with newlines using tr, then uses paste to create two columns separated by a comma from that.

If tr feels a bit too simple, you may replace it with sed 'y/,/\n/', which does the same thing.

  • Not a multi-line solution. If the file contains a line with non-even number of elements, it will be joined with next line. – Isaac May 18 at 15:40
  • 1
    @Isaac That, I presume, would indicate an issue with the data. – Kusalananda May 18 at 15:57
  • Which a robust solution should expose clearly in the output. Me thinks. – Isaac May 18 at 17:41
  • Nice. Alternately pipe tr into paste -sd ',\n' – iruvar May 18 at 20:04
4

I was able to accomplish this with the following awk command:

awk -F, -v OFS=, '{for (i=1;i<=NF;i=i+2) {j=i+1; print $i,$j}}' input

This will loop through each column in the input (incrementing by 2 each iteration) and print that column plus the next adjacent column on a line before moving to the next.

$ cat input
201012,720,201011,119,201710,16
$ awk -F, -v OFS=, '{for (i=1;i<=NF;i=i+2) {j=i+1; print $i,$j}}' input
201012,720
201011,119
201710,16
  • 1
    If the number of fields (comma separated) is not-even: a line with a trailing comma will be printed (which should be "the best we can do"). – Isaac May 18 at 15:38
  • @Isaac: A line with a trailing comma should be what you want to indicate there is an empty column in a csv file. – Jesse_b May 18 at 15:48
  • Maybe, If you think of a missing field as equal to an empty field. But I think that 1,2,3,"" is not the same as 1,2,3. ... And, the sed solution do not generate that trailing comma. – Isaac May 18 at 16:07
  • In a csv an empty field is a missing field. You are right though. 1 is not the same as 1,"". One is a csv and the other is not. – Jesse_b May 18 at 16:10
  • This is especially important when an earlier column is empty. For example ,,value1,value2 would be necessary to ensure the values end up in the proper column – Jesse_b May 18 at 16:14
1

Another sed solution:

sed 's/\([^,]*,[^,]*\),/\1\n/g' file

This replaces each second comma with a newline.

1

You could use grep - beware not all versions support -o and that this will not work for an odd number of fields

grep -E -o '[^,]+,[^,]+' file

Or some overkill

gawk 'BEGIN{FPAT="[^,]+,[^,]+";OFS="\n"}; {$1=$1; print}' file
1

Using xargs and printf:

xargs -d, printf '%s,%s\n' < file

Output:

201012,720
201011,119
201710,16

The above code assumes each line has an even number of fields. If not, xargs will print lone numbers and dangling commas. But this somewhat slower code should plow through most anything:

tr , '\n' < file | xargs -n2 printf '%s,%s\n' | sed '$s/,$//'

Which can be sped up by increasing -n2 to some reasonable maximum even number, e.g. suppose no number in the input is longer than 15 digits:

m=$(getconf ARG_MAX) m=$(( (m/16) + (m%2) ))
tr , '\n' < file | xargs -n"${m}" printf '%s,%s\n' | sed '$s/,$//'
0
awk -F'\n' -vRS=, '{l=$1; $0=""; getline; print l RS $1}'

or

awk -F'\n' -vRS=, '{print $1 RS (getline > 0 ? $1 : "")}'

You can omit the -F'\n' if the fields don't contain spaces. Or set it to the same value as the record separator (eg. with -F,) if your fields may also contain newlines (if eg. in the output of echo 1,2,3,4 the last field should be 4\n, not 4).

  • Only prints even field count. Why are not-even fields changed? – Isaac May 18 at 17:26
  • Try echo $'201012,720,201011,119,201710,16,201705\n201709,115,201708,23\n201707' | awk -F'\n' -vRS=, '{l=$1; $0=""; getline; print l","$1}'. – Isaac May 18 at 18:07
0

Shell based:

(set -f; IFS=','; printf '%s,%s\n' $(< file))

change to $(cat file) if not using ksh,bash,zsh (i.e: dash et al).

Limited by memory (or other resource limits in use for the shell instance used) in shells that implement printf as a builtin (most shells). Or by the value of ARG_MAX in shells (i.e. ksh88) that call execve to execute the command line.

Not the answer you're looking for? Browse other questions tagged or ask your own question.