1 /* dircolors - output commands to set the LS_COLOR environment variable
2 Copyright (C) 1996-2024 Free Software Foundation, Inc.
3 Copyright (C) 1994, 1995, 1997, 1998, 1999, 2000 H. Peter Anvin
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
20 #include <sys/types.h>
25 #include "dircolors.h"
27 #include "c-strcase.h"
32 /* The official name of this program (e.g., no 'g' prefix). */
33 #define PROGRAM_NAME "dircolors"
35 #define AUTHORS proper_name ("H. Peter Anvin")
37 #define obstack_chunk_alloc malloc
38 #define obstack_chunk_free free
47 #define APPEND_CHAR(C) obstack_1grow (&lsc_obstack, C)
49 /* Accumulate in this obstack the value for the LS_COLORS environment
51 static struct obstack lsc_obstack
;
53 static char const *const slack_codes
[] =
55 "NORMAL", "NORM", "FILE", "RESET", "DIR", "LNK", "LINK",
56 "SYMLINK", "ORPHAN", "MISSING", "FIFO", "PIPE", "SOCK", "BLK", "BLOCK",
57 "CHR", "CHAR", "DOOR", "EXEC", "LEFT", "LEFTCODE", "RIGHT", "RIGHTCODE",
58 "END", "ENDCODE", "SUID", "SETUID", "SGID", "SETGID", "STICKY",
59 "OTHER_WRITABLE", "OWR", "STICKY_OTHER_WRITABLE", "OWT", "CAPABILITY",
60 "MULTIHARDLINK", "CLRTOEOL", nullptr
63 static char const *const ls_codes
[] =
65 "no", "no", "fi", "rs", "di", "ln", "ln", "ln", "or", "mi", "pi", "pi",
66 "so", "bd", "bd", "cd", "cd", "do", "ex", "lc", "lc", "rc", "rc", "ec", "ec",
67 "su", "su", "sg", "sg", "st", "ow", "ow", "tw", "tw", "ca", "mh", "cl",
70 static_assert (ARRAY_CARDINALITY (slack_codes
) == ARRAY_CARDINALITY (ls_codes
));
72 /* Whether to output escaped ls color codes for display. */
73 static bool print_ls_colors
;
75 /* For long options that have no equivalent short option, use a
76 non-character as a pseudo short option, starting with CHAR_MAX + 1. */
79 PRINT_LS_COLORS_OPTION
= CHAR_MAX
+ 1,
82 static struct option
const long_options
[] =
84 {"bourne-shell", no_argument
, nullptr, 'b'},
85 {"sh", no_argument
, nullptr, 'b'},
86 {"csh", no_argument
, nullptr, 'c'},
87 {"c-shell", no_argument
, nullptr, 'c'},
88 {"print-database", no_argument
, nullptr, 'p'},
89 {"print-ls-colors", no_argument
, nullptr, PRINT_LS_COLORS_OPTION
},
90 {GETOPT_HELP_OPTION_DECL
},
91 {GETOPT_VERSION_OPTION_DECL
},
92 {nullptr, 0, nullptr, 0}
98 if (status
!= EXIT_SUCCESS
)
102 printf (_("Usage: %s [OPTION]... [FILE]\n"), program_name
);
104 Output commands to set the LS_COLORS environment variable.\n\
106 Determine format of output:\n\
107 -b, --sh, --bourne-shell output Bourne shell code to set LS_COLORS\n\
108 -c, --csh, --c-shell output C shell code to set LS_COLORS\n\
109 -p, --print-database output defaults\n\
110 --print-ls-colors output fully escaped colors for display\n\
112 fputs (HELP_OPTION_DESCRIPTION
, stdout
);
113 fputs (VERSION_OPTION_DESCRIPTION
, stdout
);
116 If FILE is specified, read it to determine which colors to use for which\n\
117 file types and extensions. Otherwise, a precompiled database is used.\n\
118 For details on the format of these files, run 'dircolors --print-database'.\n\
120 emit_ancillary_info (PROGRAM_NAME
);
126 /* If the SHELL environment variable is set to 'csh' or 'tcsh,'
127 assume C shell. Else Bourne shell. */
129 static enum Shell_syntax
130 guess_shell_syntax (void)
134 shell
= getenv ("SHELL");
135 if (shell
== nullptr || *shell
== '\0')
136 return SHELL_SYNTAX_UNKNOWN
;
138 shell
= last_component (shell
);
140 if (STREQ (shell
, "csh") || STREQ (shell
, "tcsh"))
141 return SHELL_SYNTAX_C
;
143 return SHELL_SYNTAX_BOURNE
;
147 parse_line (char const *line
, char **keyword
, char **arg
)
150 char const *keyword_start
;
151 char const *arg_start
;
156 for (p
= line
; c_isspace (to_uchar (*p
)); ++p
)
159 /* Ignore blank lines and shell-style comments. */
160 if (*p
== '\0' || *p
== '#')
165 while (!c_isspace (to_uchar (*p
)) && *p
!= '\0')
170 *keyword
= ximemdup0 (keyword_start
, p
- keyword_start
);
178 while (c_isspace (to_uchar (*p
)));
180 if (*p
== '\0' || *p
== '#')
185 while (*p
!= '\0' && *p
!= '#')
188 for (--p
; c_isspace (to_uchar (*p
)); --p
)
192 *arg
= ximemdup0 (arg_start
, p
- arg_start
);
195 /* Accumulate STR to LS_COLORS data.
196 If outputting shell syntax, then escape appropriately. */
199 append_quoted (char const *str
)
201 bool need_backslash
= true;
205 if (! print_ls_colors
)
212 need_backslash
= true;
217 need_backslash
= !need_backslash
;
227 need_backslash
= true;
236 /* Accumulate entry to LS_COLORS data.
237 Use shell syntax unless PRINT_LS_COLORS is set. */
240 append_entry (char prefix
, char const *item
, char const *arg
)
244 append_quoted ("\x1B[");
249 APPEND_CHAR (prefix
);
250 append_quoted (item
);
251 APPEND_CHAR (print_ls_colors
? '\t' : '=');
254 append_quoted ("\x1B[0m");
255 APPEND_CHAR (print_ls_colors
? '\n' : ':');
258 /* Read the file open on FP (with name FILENAME). First, look for a
259 'TERM name' directive where name matches the current terminal type.
260 Once found, translate and accumulate the associated directives onto
261 the global obstack LSC_OBSTACK. Give a diagnostic
262 upon failure (unrecognized keyword is the only way to fail here).
263 Return true if successful. */
266 dc_parse_stream (FILE *fp
, char const *filename
)
268 idx_t line_number
= 0;
269 char const *next_G_line
= G_line
;
270 char *input_line
= nullptr;
271 size_t input_line_size
= 0;
274 char const *colorterm
;
277 /* State for the parser. */
278 enum { ST_TERMNO
, ST_TERMYES
, ST_TERMSURE
, ST_GLOBAL
} state
= ST_GLOBAL
;
280 /* Get terminal type */
281 term
= getenv ("TERM");
282 if (term
== nullptr || *term
== '\0')
285 /* Also match $COLORTERM. */
286 colorterm
= getenv ("COLORTERM");
287 if (colorterm
== nullptr)
288 colorterm
= ""; /* Doesn't match default "?*" */
299 if (getline (&input_line
, &input_line_size
, fp
) <= 0)
303 error (0, errno
, _("%s: read error"), quotef (filename
));
313 if (next_G_line
== G_line
+ sizeof G_line
)
316 next_G_line
+= strlen (next_G_line
) + 1;
319 parse_line (line
, &keywd
, &arg
);
321 if (keywd
== nullptr)
326 error (0, 0, _("%s:%td: invalid line; missing second token"),
327 quotef (filename
), line_number
);
333 unrecognized
= false;
334 if (c_strcasecmp (keywd
, "TERM") == 0)
336 if (state
!= ST_TERMSURE
)
337 state
= fnmatch (arg
, term
, 0) == 0 ? ST_TERMSURE
: ST_TERMNO
;
339 else if (c_strcasecmp (keywd
, "COLORTERM") == 0)
341 if (state
!= ST_TERMSURE
)
342 state
= fnmatch (arg
, colorterm
, 0) == 0 ? ST_TERMSURE
: ST_TERMNO
;
346 if (state
== ST_TERMSURE
)
347 state
= ST_TERMYES
; /* Another {COLOR,}TERM can cancel. */
349 if (state
!= ST_TERMNO
)
352 append_entry ('*', keywd
, arg
);
353 else if (keywd
[0] == '*')
354 append_entry (0, keywd
, arg
);
355 else if (c_strcasecmp (keywd
, "OPTIONS") == 0
356 || c_strcasecmp (keywd
, "COLOR") == 0
357 || c_strcasecmp (keywd
, "EIGHTBIT") == 0)
365 for (i
= 0; slack_codes
[i
] != nullptr; ++i
)
366 if (c_strcasecmp (keywd
, slack_codes
[i
]) == 0)
369 if (slack_codes
[i
] != nullptr)
370 append_entry (0, ls_codes
[i
], arg
);
379 if (unrecognized
&& (state
== ST_TERMSURE
|| state
== ST_TERMYES
))
381 error (0, 0, _("%s:%td: unrecognized keyword %s"),
382 (filename
? quotef (filename
) : _("<internal>")),
395 dc_parse_file (char const *filename
)
399 if (! STREQ (filename
, "-") && freopen (filename
, "r", stdin
) == nullptr)
401 error (0, errno
, "%s", quotef (filename
));
405 ok
= dc_parse_stream (stdin
, filename
);
407 if (fclose (stdin
) != 0)
409 error (0, errno
, "%s", quotef (filename
));
417 main (int argc
, char **argv
)
421 enum Shell_syntax syntax
= SHELL_SYNTAX_UNKNOWN
;
422 bool print_database
= false;
424 initialize_main (&argc
, &argv
);
425 set_program_name (argv
[0]);
426 setlocale (LC_ALL
, "");
427 bindtextdomain (PACKAGE
, LOCALEDIR
);
428 textdomain (PACKAGE
);
430 atexit (close_stdout
);
432 while ((optc
= getopt_long (argc
, argv
, "bcp", long_options
, nullptr)) != -1)
435 case 'b': /* Bourne shell syntax. */
436 syntax
= SHELL_SYNTAX_BOURNE
;
439 case 'c': /* C shell syntax. */
440 syntax
= SHELL_SYNTAX_C
;
444 print_database
= true;
447 case PRINT_LS_COLORS_OPTION
:
448 print_ls_colors
= true;
451 case_GETOPT_HELP_CHAR
;
453 case_GETOPT_VERSION_CHAR (PROGRAM_NAME
, AUTHORS
);
456 usage (EXIT_FAILURE
);
462 /* It doesn't make sense to use --print with either of
463 --bourne or --c-shell. */
464 if ((print_database
| print_ls_colors
) && syntax
!= SHELL_SYNTAX_UNKNOWN
)
467 _("the options to output non shell syntax,\n"
468 "and to select a shell syntax are mutually exclusive"));
469 usage (EXIT_FAILURE
);
472 if (print_database
&& print_ls_colors
)
475 _("options --print-database and --print-ls-colors "
476 "are mutually exclusive"));
477 usage (EXIT_FAILURE
);
480 if ((!print_database
) < argc
)
482 error (0, 0, _("extra operand %s"),
483 quote (argv
[!print_database
]));
485 fprintf (stderr
, "%s\n",
486 _("file operands cannot be combined with "
487 "--print-database (-p)"));
488 usage (EXIT_FAILURE
);
493 char const *p
= G_line
;
494 while (p
- G_line
< sizeof G_line
)
502 /* If shell syntax was not explicitly specified, try to guess it. */
503 if (syntax
== SHELL_SYNTAX_UNKNOWN
&& ! print_ls_colors
)
505 syntax
= guess_shell_syntax ();
506 if (syntax
== SHELL_SYNTAX_UNKNOWN
)
507 error (EXIT_FAILURE
, 0,
508 _("no SHELL environment variable,"
509 " and no shell type option given"));
512 obstack_init (&lsc_obstack
);
514 ok
= dc_parse_stream (nullptr, nullptr);
516 ok
= dc_parse_file (argv
[0]);
520 size_t len
= obstack_object_size (&lsc_obstack
);
521 char *s
= obstack_finish (&lsc_obstack
);
525 if (syntax
== SHELL_SYNTAX_BOURNE
)
527 prefix
= "LS_COLORS='";
528 suffix
= "';\nexport LS_COLORS\n";
532 prefix
= "setenv LS_COLORS '";
535 if (! print_ls_colors
)
536 fputs (prefix
, stdout
);
537 fwrite (s
, 1, len
, stdout
);
538 if (! print_ls_colors
)
539 fputs (suffix
, stdout
);
543 return ok
? EXIT_SUCCESS
: EXIT_FAILURE
;