05bf8433d3e4b596d108405de1bd228c13d2bc63
[platform/upstream/coreutils.git] / src / cut.c
1 /* cut - remove parts of lines of files
2    Copyright (C) 1984, 1997, 1998, 1999 by David M. Ihnat
3
4    This program is a total rewrite of the Bell Laboratories Unix(Tm)
5    command of the same name, as of System V.  It contains no proprietary
6    code, and therefore may be used without violation of any proprietary
7    agreements whatsoever.  However, you will notice that the program is
8    copyrighted by me.  This is to assure the program does *not* fall
9    into the public domain.  Thus, I may specify just what I am now:
10    This program may be freely copied and distributed, provided this notice
11    remains; it may not be sold for profit without express written consent of
12    the author.
13    Please note that I recreated the behavior of the Unix(Tm) 'cut' command
14    as faithfully as possible; however, I haven't run a full set of regression
15    tests.  Thus, the user of this program accepts full responsibility for any
16    effects or loss; in particular, the author is not responsible for any losses,
17    explicit or incidental, that may be incurred through use of this program.
18
19    I ask that any bugs (and, if possible, fixes) be reported to me when
20    possible.  -David Ihnat (312) 784-4544 ignatz@homebru.chi.il.us
21
22    POSIX changes, bug fixes, long-named options, and cleanup
23    by David MacKenzie <djm@gnu.ai.mit.edu>.
24
25    Rewrite cut_fields and cut_bytes -- Jim Meyering (meyering@comco.com).
26
27    Options:
28    --bytes=byte-list
29    -b byte-list                 Print only the bytes in positions listed
30                                 in BYTE-LIST.
31                                 Tabs and backspaces are treated like any
32                                 other character; they take up 1 byte.
33
34    --characters=character-list
35    -c character-list            Print only characters in positions listed
36                                 in CHARACTER-LIST.
37                                 The same as -b for now, but
38                                 internationalization will change that.
39                                 Tabs and backspaces are treated like any
40                                 other character; they take up 1 character.
41
42    --fields=field-list
43    -f field-list                Print only the fields listed in FIELD-LIST.
44                                 Fields are separated by a TAB by default.
45
46    --delimiter=delim
47    -d delim                     For -f, fields are separated by the first
48                                 character in DELIM instead of TAB.
49
50    -n                           Do not split multibyte chars (no-op for now).
51
52    --only-delimited
53    -s                           For -f, do not print lines that do not contain
54                                 the field separator character.
55
56    The BYTE-LIST, CHARACTER-LIST, and FIELD-LIST are one or more numbers
57    or ranges separated by commas.  The first byte, character, and field
58    are numbered 1.
59
60    A FILE of `-' means standard input. */
61
62 #include <config.h>
63
64 #include <stdio.h>
65 #include <assert.h>
66 #include <getopt.h>
67 #include <sys/types.h>
68 #include "system.h"
69 #include "long-options.h"
70 #include "error.h"
71
72 /* The official name of this program (e.g., no `g' prefix).  */
73 #define PROGRAM_NAME "cut"
74
75 char *xstrdup ();
76
77 #define FATAL_ERROR(Message)                                            \
78   do                                                                    \
79     {                                                                   \
80       error (0, 0, (Message));                                          \
81       usage (2);                                                        \
82     }                                                                   \
83   while (0)
84
85 /* Append LOW, HIGH to the list RP of range pairs, allocating additional
86    space if necessary.  Update local variable N_RP.  When allocating,
87    update global variable N_RP_ALLOCATED.  */
88
89 #define ADD_RANGE_PAIR(rp, low, high)                                   \
90   do                                                                    \
91     {                                                                   \
92       if (n_rp >= n_rp_allocated)                                       \
93         {                                                               \
94           n_rp_allocated *= 2;                                          \
95           (rp) = (struct range_pair *) xrealloc ((char *) (rp),         \
96                                    n_rp_allocated * sizeof (*(rp)));    \
97         }                                                               \
98       rp[n_rp].lo = (low);                                              \
99       rp[n_rp].hi = (high);                                             \
100       ++n_rp;                                                           \
101     }                                                                   \
102   while (0)
103
104 struct range_pair
105   {
106     unsigned int lo;
107     unsigned int hi;
108   };
109
110 /* This buffer is used to support the semantics of the -s option
111    (or lack of same) when the specified field list includes (does
112    not include) the first field.  In both of those cases, the entire
113    first field must be read into this buffer to determine whether it
114    is followed by a delimiter or a newline before any of it may be
115    output.  Otherwise, cut_fields can do the job without using this
116    buffer.  */
117 static char *field_1_buffer;
118
119 /* The number of bytes allocated for FIELD_1_BUFFER.  */
120 static int field_1_bufsize;
121
122 /* The largest field or byte index used as an endpoint of a closed
123    or degenerate range specification;  this doesn't include the starting
124    index of right-open-ended ranges.  For example, with either range spec
125    `2-5,9-', `2-3,5,9-' this variable would be set to 5.  */
126 static unsigned int max_range_endpoint;
127
128 /* If nonzero, this is the index of the first field in a range that goes
129    to end of line. */
130 static unsigned int eol_range_start;
131
132 /* In byte mode, which bytes to output.
133    In field mode, which DELIM-separated fields to output.
134    Both bytes and fields are numbered starting with 1,
135    so the zeroth element of this array is unused.
136    A field or byte K has been selected if
137    (K <= MAX_RANGE_ENDPOINT and PRINTABLE_FIELD[K])
138     || (EOL_RANGE_START > 0 && K >= EOL_RANGE_START).  */
139 static int *printable_field;
140
141 enum operating_mode
142   {
143     undefined_mode,
144
145     /* Output characters that are in the given bytes. */
146     byte_mode,
147
148     /* Output the given delimeter-separated fields. */
149     field_mode
150   };
151
152 /* The name this program was run with. */
153 char *program_name;
154
155 static enum operating_mode operating_mode;
156
157 /* If nonzero do not output lines containing no delimeter characters.
158    Otherwise, all such lines are printed.  This option is valid only
159    with field mode.  */
160 static int suppress_non_delimited;
161
162 /* The delimeter character for field mode. */
163 static int delim;
164
165 /* The length of output_delimiter_string.  */
166 static size_t output_delimiter_length;
167
168 /* The output field separator string.  Defaults to the 1-character
169    string consisting of the input delimiter.  */
170 static char *output_delimiter_string;
171
172 /* Nonzero if we have ever read standard input. */
173 static int have_read_stdin;
174
175 static struct option const longopts[] =
176 {
177   {"bytes", required_argument, 0, 'b'},
178   {"characters", required_argument, 0, 'c'},
179   {"fields", required_argument, 0, 'f'},
180   {"delimiter", required_argument, 0, 'd'},
181   {"only-delimited", no_argument, 0, 's'},
182   {"output-delimiter", required_argument, 0, CHAR_MAX + 1},
183   {0, 0, 0, 0}
184 };
185
186 void
187 usage (int status)
188 {
189   if (status != 0)
190     fprintf (stderr, _("Try `%s --help' for more information.\n"),
191              program_name);
192   else
193     {
194       printf (_("\
195 Usage: %s [OPTION]... [FILE]...\n\
196 "),
197               program_name);
198       printf (_("\
199 Print selected parts of lines from each FILE to standard output.\n\
200 \n\
201   -b, --bytes=LIST        output only these bytes\n\
202   -c, --characters=LIST   output only these characters\n\
203   -d, --delimiter=DELIM   use DELIM instead of TAB for field delimiter\n\
204   -f, --fields=LIST       output only these fields\n\
205   -n                      (ignored)\n\
206   -s, --only-delimited    do not print lines not containing delimiters\n\
207       --output-delimiter=STRING  use STRING as the output delimiter\n\
208                             the default is to use the input delimiter\n\
209       --help              display this help and exit\n\
210       --version           output version information and exit\n\
211 \n\
212 Use one, and only one of -b, -c or -f.  Each LIST is made up of one\n\
213 range, or many ranges separated by commas.  Each range is one of:\n\
214 \n\
215   N     N'th byte, character or field, counted from 1\n\
216   N-    from N'th byte, character or field, to end of line\n\
217   N-M   from N'th to M'th (included) byte, character or field\n\
218   -M    from first to M'th (included) byte, character or field\n\
219 \n\
220 With no FILE, or when FILE is -, read standard input.\n\
221 "));
222       puts (_("\nReport bugs to <bug-textutils@gnu.org>."));
223     }
224   exit (status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
225 }
226
227 /* The following function was copied from getline.c, but with these changes:
228    - Read up to and including a newline or TERMINATOR, whichever comes first.
229    The original does not treat newline specially.
230    - Remove unused argument, OFFSET.
231    - Use xmalloc and xrealloc instead of malloc and realloc.
232    - Declare this function static.  */
233
234 /* Always add at least this many bytes when extending the buffer.  */
235 #define MIN_CHUNK 64
236
237 /* Read up to (and including) a newline or TERMINATOR from STREAM into
238    *LINEPTR (and null-terminate it). *LINEPTR is a pointer returned from
239    xmalloc (or NULL), pointing to *N characters of space.  It is
240    xrealloc'd as necessary.  Return the number of characters read (not
241    including the null terminator), or -1 on error or EOF.  */
242
243 static int
244 getstr (char **lineptr, int *n, FILE *stream, int terminator)
245 {
246   int nchars_avail;             /* Allocated but unused chars in *LINEPTR.  */
247   char *read_pos;               /* Where we're reading into *LINEPTR. */
248
249   if (!lineptr || !n || !stream)
250     return -1;
251
252   if (!*lineptr)
253     {
254       *n = MIN_CHUNK;
255       *lineptr = (char *) xmalloc (*n);
256       if (!*lineptr)
257         return -1;
258     }
259
260   nchars_avail = *n;
261   read_pos = *lineptr;
262
263   for (;;)
264     {
265       register int c = getc (stream);
266
267       /* We always want at least one char left in the buffer, since we
268          always (unless we get an error while reading the first char)
269          NUL-terminate the line buffer.  */
270
271       assert (*n - nchars_avail == read_pos - *lineptr);
272       if (nchars_avail < 1)
273         {
274           if (*n > MIN_CHUNK)
275             *n *= 2;
276           else
277             *n += MIN_CHUNK;
278
279           nchars_avail = *n + *lineptr - read_pos;
280           *lineptr = xrealloc (*lineptr, *n);
281           if (!*lineptr)
282             return -1;
283           read_pos = *n - nchars_avail + *lineptr;
284           assert (*n - nchars_avail == read_pos - *lineptr);
285         }
286
287       if (feof (stream) || ferror (stream))
288         {
289           /* Return partial line, if any.  */
290           if (read_pos == *lineptr)
291             return -1;
292           else
293             break;
294         }
295
296       *read_pos++ = c;
297       nchars_avail--;
298
299       if (c == terminator || c == '\n')
300         /* Return the line.  */
301         break;
302     }
303
304   /* Done - NUL terminate and return the number of chars read.  */
305   *read_pos = '\0';
306
307   return read_pos - *lineptr;
308 }
309
310 static int
311 print_kth (unsigned int k)
312 {
313   return ((0 < eol_range_start && eol_range_start <= k)
314           || (k <= max_range_endpoint && printable_field[k]));
315 }
316
317 /* Given the list of field or byte range specifications FIELDSTR, set
318    MAX_RANGE_ENDPOINT and allocate and initialize the PRINTABLE_FIELD
319    array.  If there is a right-open-ended range, set EOL_RANGE_START
320    to its starting index.  FIELDSTR should be composed of one or more
321    numbers or ranges of numbers, separated by blanks or commas.
322    Incomplete ranges may be given: `-m' means `1-m'; `n-' means `n'
323    through end of line.  Return nonzero if FIELDSTR contains at least
324    one field specification, zero otherwise.  */
325
326 /* FIXME-someday:  What if the user wants to cut out the 1,000,000-th field
327    of some huge input file?  This function shouldn't have to alloate a table
328    of a million ints just so we can test every field < 10^6 with an array
329    dereference.  Instead, consider using a dynamic hash table.  It would be
330    simpler and nearly as good a solution to use a 32K x 4-byte table with
331    one bit per field index instead of a whole `int' per index.  */
332
333 static int
334 set_fields (const char *fieldstr)
335 {
336   unsigned int initial = 1;     /* Value of first number in a range.  */
337   unsigned int value = 0;       /* If nonzero, a number being accumulated.  */
338   int dash_found = 0;           /* Nonzero if a '-' is found in this field.  */
339   int field_found = 0;          /* Non-zero if at least one field spec
340                                    has been processed.  */
341
342   struct range_pair *rp;
343   unsigned int n_rp;
344   unsigned int n_rp_allocated;
345   unsigned int i;
346
347   n_rp = 0;
348   n_rp_allocated = 16;
349   rp = (struct range_pair *) xmalloc (n_rp_allocated * sizeof (*rp));
350
351   /* Collect and store in RP the range end points.
352      It also sets EOL_RANGE_START if appropriate.  */
353
354   for (;;)
355     {
356       if (*fieldstr == '-')
357         {
358           /* Starting a range. */
359           if (dash_found)
360             FATAL_ERROR (_("invalid byte or field list"));
361           dash_found++;
362           fieldstr++;
363
364           if (value)
365             {
366               initial = value;
367               value = 0;
368             }
369           else
370             initial = 1;
371         }
372       else if (*fieldstr == ',' || ISBLANK (*fieldstr) || *fieldstr == '\0')
373         {
374           /* Ending the string, or this field/byte sublist. */
375           if (dash_found)
376             {
377               dash_found = 0;
378
379               /* A range.  Possibilites: -n, m-n, n-.
380                  In any case, `initial' contains the start of the range. */
381               if (value == 0)
382                 {
383                   /* `n-'.  From `initial' to end of line. */
384                   eol_range_start = initial;
385                   field_found = 1;
386                 }
387               else
388                 {
389                   /* `m-n' or `-n' (1-n). */
390                   if (value < initial)
391                     FATAL_ERROR (_("invalid byte or field list"));
392
393                   /* Is there already a range going to end of line? */
394                   if (eol_range_start != 0)
395                     {
396                       /* Yes.  Is the new sequence already contained
397                          in the old one?  If so, no processing is
398                          necessary. */
399                       if (initial < eol_range_start)
400                         {
401                           /* No, the new sequence starts before the
402                              old.  Does the old range going to end of line
403                              extend into the new range?  */
404                           if (value + 1 >= eol_range_start)
405                             {
406                               /* Yes.  Simply move the end of line marker. */
407                               eol_range_start = initial;
408                             }
409                           else
410                             {
411                               /* No.  A simple range, before and disjoint from
412                                  the range going to end of line.  Fill it. */
413                               ADD_RANGE_PAIR (rp, initial, value);
414                             }
415
416                           /* In any case, some fields were selected. */
417                           field_found = 1;
418                         }
419                     }
420                   else
421                     {
422                       /* There is no range going to end of line. */
423                       ADD_RANGE_PAIR (rp, initial, value);
424                       field_found = 1;
425                     }
426                   value = 0;
427                 }
428             }
429           else if (value != 0)
430             {
431               /* A simple field number, not a range. */
432               ADD_RANGE_PAIR (rp, value, value);
433               value = 0;
434               field_found = 1;
435             }
436
437           if (*fieldstr == '\0')
438             {
439               break;
440             }
441
442           fieldstr++;
443         }
444       else if (ISDIGIT (*fieldstr))
445         {
446           /* FIXME: detect overflow?  */
447           value = 10 * value + *fieldstr - '0';
448           fieldstr++;
449         }
450       else
451         FATAL_ERROR (_("invalid byte or field list"));
452     }
453
454   max_range_endpoint = 0;
455   for (i = 0; i < n_rp; i++)
456     {
457       if (rp[i].hi > max_range_endpoint)
458         max_range_endpoint = rp[i].hi;
459     }
460
461   /* Allocate an array large enough so that it may be indexed by
462      the field numbers corresponding to all finite ranges
463      (i.e. `2-6' or `-4', but not `5-') in FIELDSTR.  */
464
465   printable_field = (int *) xmalloc ((max_range_endpoint + 1) * sizeof (int));
466   memset (printable_field, 0, (max_range_endpoint + 1) * sizeof (int));
467
468   /* Set the array entries corresponding to integers in the ranges of RP.  */
469   for (i = 0; i < n_rp; i++)
470     {
471       unsigned int j;
472       for (j = rp[i].lo; j <= rp[i].hi; j++)
473         {
474           printable_field[j] = 1;
475         }
476     }
477
478   free (rp);
479
480   return field_found;
481 }
482
483 /* Read from stream STREAM, printing to standard output any selected bytes.  */
484
485 static void
486 cut_bytes (FILE *stream)
487 {
488   unsigned int byte_idx;        /* Number of chars in the line so far. */
489
490   byte_idx = 0;
491   while (1)
492     {
493       register int c;           /* Each character from the file. */
494
495       c = getc (stream);
496
497       if (c == '\n')
498         {
499           putchar ('\n');
500           byte_idx = 0;
501         }
502       else if (c == EOF)
503         {
504           if (byte_idx > 0)
505             putchar ('\n');
506           break;
507         }
508       else
509         {
510           ++byte_idx;
511           if (print_kth (byte_idx))
512             {
513               putchar (c);
514             }
515         }
516     }
517 }
518
519 /* Read from stream STREAM, printing to standard output any selected fields.  */
520
521 static void
522 cut_fields (FILE *stream)
523 {
524   int c;
525   unsigned int field_idx;
526   int found_any_selected_field;
527   int buffer_first_field;
528   int empty_input;
529
530   found_any_selected_field = 0;
531   field_idx = 1;
532
533   c = getc (stream);
534   empty_input = (c == EOF);
535   if (c != EOF)
536     ungetc (c, stream);
537
538   /* To support the semantics of the -s flag, we may have to buffer
539      all of the first field to determine whether it is `delimited.'
540      But that is unnecessary if all non-delimited lines must be printed
541      and the first field has been selected, or if non-delimited lines
542      must be suppressed and the first field has *not* been selected.
543      That is because a non-delimited line has exactly one field.  */
544   buffer_first_field = (suppress_non_delimited ^ !print_kth (1));
545
546   while (1)
547     {
548       if (field_idx == 1 && buffer_first_field)
549         {
550           int len;
551
552           len = getstr (&field_1_buffer, &field_1_bufsize, stream, delim);
553           if (len < 0)
554             break;
555
556           assert (len != 0);
557
558           /* If the first field extends to the end of line (it is not
559              delimited) and we are printing all non-delimited lines,
560              print this one.  */
561           if ((unsigned char) field_1_buffer[len - 1] != delim)
562             {
563               if (suppress_non_delimited)
564                 {
565                   /* Empty.  */
566                 }
567               else
568                 {
569                   fwrite (field_1_buffer, sizeof (char), len, stdout);
570                   /* Make sure the output line is newline terminated.  */
571                   if (field_1_buffer[len - 1] != '\n')
572                     putchar ('\n');
573                 }
574               continue;
575             }
576           if (print_kth (1))
577             {
578               /* Print the field, but not the trailing delimiter.  */
579               fwrite (field_1_buffer, sizeof (char), len - 1, stdout);
580               found_any_selected_field = 1;
581             }
582           ++field_idx;
583         }
584
585       if (c != EOF)
586         {
587           if (print_kth (field_idx))
588             {
589               if (found_any_selected_field)
590                 {
591                   fwrite (output_delimiter_string, sizeof (char),
592                           output_delimiter_length, stdout);
593                 }
594               found_any_selected_field = 1;
595
596               while ((c = getc (stream)) != delim && c != '\n' && c != EOF)
597                 {
598                   putchar (c);
599                 }
600             }
601           else
602             {
603               while ((c = getc (stream)) != delim && c != '\n' && c != EOF)
604                 {
605                   /* Empty.  */
606                 }
607             }
608         }
609
610       if (c == '\n')
611         {
612           c = getc (stream);
613           if (c != EOF)
614             {
615               ungetc (c, stream);
616               c = '\n';
617             }
618         }
619
620       if (c == delim)
621         ++field_idx;
622       else if (c == '\n' || c == EOF)
623         {
624           if (found_any_selected_field
625               || (!empty_input && !(suppress_non_delimited && field_idx == 1)))
626             putchar ('\n');
627           if (c == EOF)
628             break;
629           field_idx = 1;
630           found_any_selected_field = 0;
631         }
632     }
633 }
634
635 static void
636 cut_stream (FILE *stream)
637 {
638   if (operating_mode == byte_mode)
639     cut_bytes (stream);
640   else
641     cut_fields (stream);
642 }
643
644 /* Process file FILE to standard output.
645    Return 0 if successful, 1 if not. */
646
647 static int
648 cut_file (char *file)
649 {
650   FILE *stream;
651
652   if (STREQ (file, "-"))
653     {
654       have_read_stdin = 1;
655       stream = stdin;
656     }
657   else
658     {
659       stream = fopen (file, "r");
660       if (stream == NULL)
661         {
662           error (0, errno, "%s", file);
663           return 1;
664         }
665     }
666
667   cut_stream (stream);
668
669   if (ferror (stream))
670     {
671       error (0, errno, "%s", file);
672       return 1;
673     }
674   if (STREQ (file, "-"))
675     clearerr (stream);          /* Also clear EOF. */
676   else if (fclose (stream) == EOF)
677     {
678       error (0, errno, "%s", file);
679       return 1;
680     }
681   return 0;
682 }
683
684 int
685 main (int argc, char **argv)
686 {
687   int optc, exit_status = 0;
688   int delim_specified = 0;
689
690   program_name = argv[0];
691   setlocale (LC_ALL, "");
692   bindtextdomain (PACKAGE, LOCALEDIR);
693   textdomain (PACKAGE);
694
695   parse_long_options (argc, argv, "cut", GNU_PACKAGE, VERSION,
696                       "David Ihnat, David MacKenzie, and Jim Meyering", usage);
697
698   operating_mode = undefined_mode;
699
700   /* By default, all non-delimited lines are printed.  */
701   suppress_non_delimited = 0;
702
703   delim = '\0';
704   have_read_stdin = 0;
705
706   while ((optc = getopt_long (argc, argv, "b:c:d:f:ns", longopts, NULL)) != -1)
707     {
708       switch (optc)
709         {
710         case 0:
711           break;
712
713         case 'b':
714         case 'c':
715           /* Build the byte list. */
716           if (operating_mode != undefined_mode)
717             FATAL_ERROR (_("only one type of list may be specified"));
718           operating_mode = byte_mode;
719           if (set_fields (optarg) == 0)
720             FATAL_ERROR (_("missing list of positions"));
721           break;
722
723         case 'f':
724           /* Build the field list. */
725           if (operating_mode != undefined_mode)
726             FATAL_ERROR (_("only one type of list may be specified"));
727           operating_mode = field_mode;
728           if (set_fields (optarg) == 0)
729             FATAL_ERROR (_("missing list of fields"));
730           break;
731
732         case 'd':
733           /* New delimiter. */
734           /* Interpret -d '' to mean `use the NUL byte as the delimiter.'  */
735           if (optarg[0] != '\0' && optarg[1] != '\0')
736             FATAL_ERROR (_("the delimiter must be a single character"));
737           delim = (unsigned char) optarg[0];
738           delim_specified = 1;
739           break;
740
741         case CHAR_MAX + 1:
742           /* Interpret --output-delimiter='' to mean
743              `use the NUL byte as the delimiter.'  */
744           output_delimiter_length = (optarg[0] == '\0'
745                                      ? 1 : strlen (optarg));
746           output_delimiter_string = xstrdup (optarg);
747           break;
748
749         case 'n':
750           break;
751
752         case 's':
753           suppress_non_delimited = 1;
754           break;
755
756         default:
757           usage (2);
758         }
759     }
760
761   if (operating_mode == undefined_mode)
762     FATAL_ERROR (_("you must specify a list of bytes, characters, or fields"));
763
764   if (delim != '\0' && operating_mode != field_mode)
765     FATAL_ERROR (_("a delimiter may be specified only when operating on fields"));
766
767   if (suppress_non_delimited && operating_mode != field_mode)
768     FATAL_ERROR (_("suppressing non-delimited lines makes sense\n\
769 \tonly when operating on fields"));
770
771   if (!delim_specified)
772     delim = '\t';
773
774   if (output_delimiter_string == NULL)
775     {
776       static char dummy[2];
777       dummy[0] = delim;
778       dummy[1] = '\0';
779       output_delimiter_string = dummy;
780       output_delimiter_length = 1;
781     }
782
783   if (optind == argc)
784     exit_status |= cut_file ("-");
785   else
786     for (; optind < argc; optind++)
787       exit_status |= cut_file (argv[optind]);
788
789   if (have_read_stdin && fclose (stdin) == EOF)
790     {
791       error (0, errno, "-");
792       exit_status = 1;
793     }
794   if (ferror (stdout) || fclose (stdout) == EOF)
795     error (EXIT_FAILURE, errno, _("write error"));
796
797   exit (exit_status == 0 ? EXIT_SUCCESS : EXIT_FAILURE);
798 }