39

I have more than 1000 lines in a file. The file starts as follows (line numbers added):

Station Name
Station Code
A N DEV NAGAR
ACND
ABHAIPUR
AHA
ABOHAR
ABS
ABU ROAD
ABR

I need to convert this to a file, with comma separated entries by joining every two lines. The final data should look like

Station Name,Station Code
A N DEV NAGAR,ACND
ABHAIPUR,AHA
ABOHAR,ABS
ABU ROAD,ABR
...

What I was trying was - trying to write a shell script and then echo them with comma in between. But I guess a simpler effective one-liner would do the job here may be in sed/awk.

Any ideas?

10 Answers 10

47

Simply use cat (if you like cats ;-)) and paste:

cat file.in | paste -d, - - > file.out

Explanation: paste reads from a number of files and pastes together the corresponding lines (line 1 from first file with line 1 from second file etc):

paste file1 file2 ...

Instead of a file name, we can use - (dash). paste takes first line from file1 (which is stdin). Then, it wants to read the first line from file2 (which is also stdin). However, since the first line of stdin was already read and processed, what now waits on the input stream is the second line of stdin, which paste happily glues to the first one. The -d option sets the delimiter to be a comma rather than a tab.

Alternatively, do

cat file.in | sed "N;s/\n/,/" > file.out

P.S. Yes, one can simplify the above to

< file.in sed "N;s/\n/,/" > file.out

or

< file.in paste -d, - - > file.out

which has the advantage of not using cat.

However, I did not use this idiom on purpose, for clarity reasons -- it is less verbose and I like cat (CATS ARE NICE). So please do not edit.

Alternatively, if you prefer paste to cats (paste is the command to concatenate files horizontally, while cat concatenates them vertically), you may use:

paste file.in | paste -d, - -
| improve this answer | |
  • Just to mention it again. Line numbers are not a part of file :) – mtk Oct 17 '12 at 18:06
  • The paste command perfectly works, can you please give a little more explanation about it. The hyphens ??? – mtk Oct 17 '12 at 18:08
  • 2
    The hyphens mean "read from stdin". If the same input source is repeated, paste knows to read from it several times per row of output. – dubiousjim Oct 17 '12 at 18:13
  • @sch: cool edit, I won't touch it :-) – January Oct 18 '12 at 10:35
  • 1
    With respect to your cat argument. Does sed "N;s/\n/,/" file.in > file.out not work? – Bernhard Oct 18 '12 at 11:13
8

In case anyone landing here is looking to combine all lines into a CSV one liner, try

cat file | tr '\n' ','
| improve this answer | |
3
sed 'N;s/\n/,/' file

Using sed, join(N) every 2 lines, and replace the newline(\n) with ",".

| improve this answer | |
3
paste -sd ',\n' file.in > file.out

Also note that because we're merely replacing one character with another (every other newline with a comma), we can work on the input file in place:

paste -sd ',\n' file.in 1<> file.in

(but beware it might not work on non-Unix systems that have CRLF terminators (like Microsoft ones) that some emulated POSIX paste might treat in a non-Unix way)

| improve this answer | |
  • What does that 1 is doing here in 1<>? is that a typo? – αғsнιη May 19 '18 at 2:41
  • @αғsнιη, see this – iruvar Apr 9 '19 at 4:24
  • @iruvar thank you – αғsнιη Apr 9 '19 at 4:36
2

Here is a one-liner (though potentially millions-of-commands-run-er) using pure Bash:

(IFS=; while read -r name; do read -r code; printf '%s\n" "$name,$code"; done < file.in) > file.out

I use a subshell (the paranthesis) so that I won't have to store and restore IFS. Which one otherwise should do as to not mess up the users environment in case the source is sourced. The alternative would be to pass that new IFS only to read as in IFS= read -r name, IFS= read -r code.

The fact that all the commands in the loop are built in the shell makes its performance acceptable and is even faster than the other solutions for small files. But many people would consider it bad practice and one should be careful when generalising it to anything else.

| improve this answer | |
  • in general yay for using subshells to localize environment changes. But in this case it's not needed: you can instead do while IFS='\n' read -r name; do IFS='\n' read -r code ... done < file.in, which is an idiom I often see in shell scripts. The -r flag to read means "interpret the character '\' followed by the character 'n' in the stdin stream as two characters, rather than as a newline." Arguably, it may be more aesthetic to create the subshell as you do than to repeat the IFS='\n'. – dubiousjim Oct 17 '12 at 22:10
  • @dubiousjim: The -r improved the solution technically. Great! I'm not a fan of the idea of passing a changed IFS twice. If I had used one read, super nice, but not twice. Of course that's a matter of opinion. Using a subshell is a bit over the general Bash knowledge I would say, so a lot of folks will have trouble understanding its purpose. That's a bad thing. – Deleted Oct 17 '12 at 22:41
2

For the complete set of answers, a possible awk solution may be:

awk 'NR%2==1 {printf $0","} NR%2==0 { print $0}' *file*
| improve this answer | |
  • @downvoter: What is wrong with my answer to deserve a downvote? How can it be improved? – Bernhard Oct 18 '12 at 20:38
  • Maybe because the lazy printf? Will fail in the rare case when a station name contains a format specifier. (See pastebin.com/wgxFttrJ for an example.) But this is just a guess, the downvote is not from me. – manatwork Oct 19 '12 at 9:54
1

Hoary old chestnut of an awkidiom

awk '{ORS=NR%2?",":"\n";print}' file
Station Name,Station Code
A N DEV NAGAR,ACND
ABHAIPUR,AHA
ABOHAR,ABS
ABU ROAD,ABR
| improve this answer | |
  • awk '{ORS=NR%2?",":"\n"};1' is shorter and more idiom – cuonglm Aug 29 '15 at 1:48
  • @cuonglm, i doubt it. In this instance it's still a one-liner despite the print and the intent is clear. 1 is just as clear to old awk hands such as myself but I prefer print – iruvar Aug 29 '15 at 2:09
  • This was the first simple solution that I found that was easily configurable to more than 2 lines. I fought with sed for a while before searching, but awk made combining every 4 lines easier. Saved me a trip to the $EDITOR! – opello Feb 26 '16 at 17:37
0

Possible with perl too,

perl -pe 's/^\d+\.\s+//;$.&1?chomp:print","' file

| improve this answer | |
0

For example:

seq 0 70 | xargs -L 2 | sed 's/ /,/g'

Output: ( note: xargs -L number_of_columns works nicely with most any number of columns not just every two lines)

0,1
2,3
4,5
6,7
8,9
10,11
12,13
14,15
16,17
18,19
20,21
22,23
24,25
26,27
28,29
30,31
32,33
34,35
36,37
38,39
40,41
42,43
44,45
46,47
48,49
50,51
52,53
54,55
56,57
58,59
60,61
62,63
64,65
66,67
68,69
70
| improve this answer | |
0

POSIX solution with pr:

pr -2 -a -t -s, file

http://pubs.opengroup.org/onlinepubs/9699919799/utilities/pr.html

| improve this answer | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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