]> git.ipfire.org Git - thirdparty/coreutils.git/commitdiff
dirname: quote problematic names on tty
authorCollin Funk <collin.funk1@gmail.com>
Sun, 2 Aug 2026 02:38:50 +0000 (19:38 -0700)
committerCollin Funk <collin.funk1@gmail.com>
Sun, 2 Aug 2026 18:13:24 +0000 (11:13 -0700)
* doc/coreutils.texi (terminalQuoted): New macro.
(readlink invocation, basename invocation, dirname invocation): Use it.
* src/dirname.c (quote_output): New variable.
(main): Quote output when appropriate.
* src/system.h (quoteN_mem): New macro.
* tests/misc/dirname.pl: Add test cases based on the ones added for
basename in commit e897dfd02 (basename: quote problematic names on tty,
2026-07-30). Copy some logic from tests/misc/basename.pl to test the -z
option.
* tests/misc/tty-quoting.sh: Also test dirname.
* NEWS: Mention the improvement.

NEWS
doc/coreutils.texi
src/dirname.c
src/system.h
tests/misc/dirname.pl
tests/misc/tty-quoting.sh

diff --git a/NEWS b/NEWS
index 3a7b175e30801cb991c241d77cd16e133bdaf7a8..a85b0ada9d3cfe525de66c311a950057f80968f8 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -84,9 +84,9 @@ GNU coreutils NEWS                                    -*- outline -*-
   'install -C' will now avoid updating file metadata when the destination
   already has the appropriate ownership and permissions.
 
-  'basename', 'du', 'readlink', and 'realpath' now quote output in shell-escape
-  style when standard output is a terminal.  The QUOTING_STYLE environment
-  variable can be used to adjust or disable the quoting.
+  'basename', 'dirname', 'du', 'readlink', and 'realpath' now quote output in
+  shell-escape style when standard output is a terminal.  The QUOTING_STYLE
+  environment variable can be used to adjust or disable the quoting.
 
   'ls -m' now quotes files names containing commas when appropriate,
   so users can better distinguish separating commas.
index 441b80c3c5f1f9b01338b1982c56375976b1977a..939b0ccae6772801179b63c0ed44a61635cd90b8 100644 (file)
@@ -11100,10 +11100,15 @@ variable is set.
 
 @end table
 
+@c This is also used by basename and dirname.
+@macro terminalQuoted
 When standard output is a terminal, output is quoted using the
 @samp{shell-escape} style.  The environment variable
-@env{QUOTING_STYLE} can select the quoting style. Valid quoting styles are:
+@env{QUOTING_STYLE} specifies the quoting style.  Valid quoting styles
+are:
 @quotingStyles
+@end macro
+@terminalQuoted
 
 The @command{readlink} utility first appeared in OpenBSD 2.1.
 
@@ -14203,10 +14208,7 @@ This option implies the @option{-a} option.
 
 @end table
 
-When standard output is a terminal, output is quoted using the
-@samp{shell-escape} style.  The environment variable
-@env{QUOTING_STYLE} specifies the quoting style.  Valid quoting styles are:
-@quotingStyles
+@terminalQuoted
 
 @exitstatus
 
@@ -14263,6 +14265,8 @@ The program accepts the following option.  Also see @ref{Common options}.
 
 @end table
 
+@terminalQuoted
+
 @exitstatus
 
 Examples:
index dbd21d683e490f59f2b7832426d8dc6449208615..1a7c61bdb563c02f0869fa29254c4bd2fd7fcae7 100644 (file)
@@ -22,6 +22,7 @@
 #include <stdio.h>
 #include <sys/types.h>
 
+#include "argmatch.h"  /* argmatch($QUOTING_STYLE).  */
 #include "system.h"
 
 /* The official name of this program (e.g., no 'g' prefix).  */
@@ -31,6 +32,8 @@
   proper_name ("David MacKenzie"), \
   proper_name ("Jim Meyering")
 
+static bool quote_output;
+
 static struct option const longopts[] =
 {
   {"zero", no_argument, NULL, 'z'},
@@ -114,6 +117,19 @@ main (int argc, char **argv)
       usage (EXIT_FAILURE);
     }
 
+
+  if (!use_nuls && isatty (STDOUT_FILENO))
+    {
+      int qs = getenv_quoting_style ();
+      if (qs < 0)
+        qs = shell_escape_quoting_style;
+      if (qs != literal_quoting_style)
+        {
+          set_quoting_style (NULL, qs);
+          quote_output = true;
+        }
+    }
+
   for (; optind < argc; optind++)
     {
       char const *result = argv[optind];
@@ -125,7 +141,11 @@ main (int argc, char **argv)
           result = &dot;
           len = 1;
         }
-
+      if (quote_output)
+        {
+          result = quoteN_mem (result, len);
+          len = strlen (result);
+        }
       fwrite (result, 1, len, stdout);
       putchar (use_nuls ? '\0' :'\n');
     }
index 7efc78972d2b3cb06865682f3767fe63ff0f579d..017491d988da4bc13a1f08f01ae598a703f93d6d 100644 (file)
@@ -1004,6 +1004,8 @@ is_ENOTSUP (int err)
 
 /* Equivalent to quotearg(), but explicit to avoid syntax checks.  */
 #define quoteN(x) quotearg_style (get_quoting_style (NULL), x)
+#define quoteN_mem(x, size)                                     \
+  quotearg_style_mem (get_quoting_style (NULL), x, size)
 
 #ifdef ARGMATCH
 /* Return the quoting style specified by the environment variable
index 47701750f3f24c5dba12b59e16148bed5f2d81de..0b79cdee9b2557e3efb5f717e5e0d5bf3fa7bf68 100755 (executable)
@@ -50,9 +50,21 @@ my @Tests =
      ['l', qw(///a//b/),   {OUT => '///a'}],
      ['m', qw(''),         {OUT => '.'}],
      ['n', qw(a/b c/d),    {OUT => "a\nc"}],
+
+     # QUOTING_STYLE does not affect redirected output.
+     ['q-lit', q{'q name/f'}, {ENV => 'QUOTING_STYLE=literal'},
+      {OUT => 'q name'}],
+     ['q-shell', q{'q name/f'}, {ENV => 'QUOTING_STYLE=shell-always'},
+      {OUT => 'q name'}],
+     ['q-invalid', q{'q name/f'}, {ENV => 'QUOTING_STYLE=invalid'},
+      {OUT => 'q name'}],
+
+     ['z-quote', q{-z 'q name/f'}, {ENV => 'QUOTING_STYLE=invalid'},
+      {OUT => "q name\0"}],
     );
 
 # Append a newline to end of each expected 'OUT' string.
+# Skip -z tests, i.e., those whose 'OUT' string has a trailing '\0'.
 my $t;
 foreach $t (@Tests)
   {
@@ -61,7 +73,8 @@ foreach $t (@Tests)
     foreach $e (@$t)
       {
         $e->{OUT} = "$e->{OUT}\n"
-          if ref $e eq 'HASH' and exists $e->{OUT};
+          if ref $e eq 'HASH' and exists $e->{OUT}
+            and not $e->{OUT} =~ /\0$/;
       }
   }
 
index fe3f81289401a115512b947a6466dc157f5585f6..a0dc1d4b8ec45ea210e52a2207628eef00379d8a 100755 (executable)
@@ -17,7 +17,7 @@
 # along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 . "${srcdir=.}/tests/init.sh"; path_prepend_ ./src
-print_ver_ basename du ls readlink realpath printf test
+print_ver_ basename dirname du ls readlink realpath printf test
 require_strace_ ioctl
 
 touch 'b ar' || framework_failure_
@@ -44,15 +44,16 @@ run_tty_ env test -t 1 ||
 run_tty_ env printf foo >printf.t &&
  skip_ 'libc buffering induced a tty probe'
 
-for cmd in basename du 'ls -w0' readlink 'realpath --relative-to=.'; do
+for cmd in basename du dirname 'ls -w0' readlink 'realpath --relative-to=.'; do
   test "$cmd" = 'du' && field=2 || field=1
+  test "$cmd" = 'dirname' && file='f oo/.' || file='f oo'
 
-  run_tty_ $cmd 'f oo' >quoted.t || fail=1
+  run_tty_ $cmd "$file" >quoted.t || fail=1
   cut -f$field- quoted.t >quoted || framework_failure_
 
   # Note ls theoretically doesn't need isatty() for a specified QUOTING_STYLE
   # but it does need it to determine appropriate output format.
-  QUOTING_STYLE=literal run_tty_ $cmd 'f oo' >unquoted.t || fail=1
+  QUOTING_STYLE=literal run_tty_ $cmd "$file" >unquoted.t || fail=1
   cut -f$field- unquoted.t >unquoted || framework_failure_
 
   env printf '%q\n' "$(cat unquoted)" >printf_quoted || framework_failure_