4eab13839e761454787d9d4c80652f07c322046a
[oweals/busybox.git] / coreutils / paste.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * paste.c - implementation of the posix paste command
4  *
5  * Written by Maxime Coste <mawww@kakoune.org>
6  *
7  * Licensed under GPLv2 or later, see file LICENSE in this source tree.
8  */
9 //config:config PASTE
10 //config:       bool "paste"
11 //config:       default y
12 //config:       help
13 //config:         paste is used to paste lines of different files together
14 //config:         and write the result to stdout
15
16 //applet:IF_PASTE(APPLET_NOEXEC(paste, paste, BB_DIR_USR_BIN, BB_SUID_DROP, paste))
17
18 //kbuild:lib-$(CONFIG_PASTE) += paste.o
19
20 //usage:#define paste_trivial_usage
21 //usage:       "[OPTIONS] [FILE]..."
22 //usage:#define paste_full_usage "\n\n"
23 //usage:       "Paste lines from each input file, seperated with tab\n"
24 //usage:     "\n        -d LIST Use delimiters from LIST, not tab"
25 //usage:     "\n        -s      Serial: one file at a time"
26 //usage:
27 //usage:#define paste_example_usage
28 //usage:       "# write out directory in four columns\n"
29 //usage:       "$ ls | paste - - - -\n"
30 //usage:       "# combine pairs of lines from a file into single lines\n"
31 //usage:       "$ paste -s -d '\\t\\n' file\n"
32
33 #include "libbb.h"
34
35 static void paste_files(FILE** files, int file_cnt, char* delims, int del_cnt)
36 {
37         char *line;
38         char delim;
39         int del_idx = 0;
40         int active_files = file_cnt;
41         int i;
42
43         while (active_files > 0) {
44                 for (i = 0; i < file_cnt; ++i) {
45                         if (files[i] == NULL)
46                                 continue;
47
48                         line = xmalloc_fgetline(files[i]);
49                         if (!line) {
50                                 fclose_if_not_stdin(files[i]);
51                                 files[i] = NULL;
52                                 --active_files;
53                                 continue;
54                         }
55                         fputs(line, stdout);
56                         free(line);
57                         delim = '\n';
58                         if (i != file_cnt - 1) {
59                                 delim = delims[del_idx++];
60                                 if (del_idx == del_cnt)
61                                         del_idx = 0;
62                         }
63                         if (delim != '\0')
64                                 fputc(delim, stdout);
65                 }
66         }
67 }
68
69 static void paste_files_separate(FILE** files, char* delims, int del_cnt)
70 {
71         char *line, *next_line;
72         char delim;
73         int del_idx = 0;
74         int i;
75
76         for (i = 0; files[i]; ++i) {
77                 line = NULL;
78                 while ((next_line = xmalloc_fgetline(files[i])) != NULL) {
79                         if (line) {
80                                 fputs(line, stdout);
81                                 free(line);
82                                 delim = delims[del_idx++];
83                                 if (del_idx == del_cnt)
84                                         del_idx = 0;
85                                 if (delim != '\0')
86                                         fputc(delim, stdout);
87                         }
88                         line = next_line;
89                 }
90                 if (line) {
91                         /* coreutils adds \n even if this is a final line
92                          * of the last file and it was not \n-terminated.
93                          */
94                         printf("%s\n", line);
95                         free(line);
96                 }
97                 fclose_if_not_stdin(files[i]);
98         }
99 }
100
101 #define PASTE_OPT_DELIMITERS (1 << 0)
102 #define PASTE_OPT_SEPARATE   (1 << 1)
103
104 int paste_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
105 int paste_main(int argc UNUSED_PARAM, char **argv)
106 {
107         char *delims = (char*)"\t";
108         int del_cnt = 1;
109         unsigned opt;
110         int i;
111
112         opt = getopt32(argv, "d:s", &delims);
113         argv += optind;
114
115         if (opt & PASTE_OPT_DELIMITERS) {
116                 if (!delims[0])
117                         bb_error_msg_and_die("-d '' is not supported");
118                 /* unknown mappings are not changed: "\z" -> '\\' 'z' */
119                 /* trailing backslash, if any, is preserved */
120                 del_cnt = strcpy_and_process_escape_sequences(delims, delims) - delims;
121                 /* note: handle NUL properly (do not stop at it!): try -d'\t\0\t' */
122         }
123
124         if (!argv[0])
125                 (--argv)[0] = (char*) "-";
126         for (i = 0; argv[i]; ++i) {
127                 argv[i] = (void*) fopen_or_warn_stdin(argv[i]);
128                 if (!argv[i])
129                         xfunc_die();
130         }
131
132         if (opt & PASTE_OPT_SEPARATE)
133                 paste_files_separate((FILE**)argv, delims, del_cnt);
134         else
135                 paste_files((FILE**)argv, i, delims, del_cnt);
136
137         fflush_stdout_and_exit(0);
138 }