@opindex --quoting-style
Do not quote file names. However, with @command{ls} nongraphic
characters are still printed as question marks if the output is a
-terminal and you do not specify the @option{--show-control-chars}
-option.
+terminal and you do not specify the @option{--show-control-chars} option.
+Also newline characters in file names are printed as question marks
+if ambiguous with separators, irrespective of whether output
+is a terminal or not.
@optItem{ls,-q,}
@optItemx{ls,--hide-control-chars,}
quoting methods pass through control chars as-is. */
static bool qmark_funny_chars;
+/* True means extra replacement of newline with '?'
+ in cases where a newline in a file name would be
+ ambiguous with newline separators. */
+static bool qmark_newline;
+
/* Quoting options for file and dir name output. */
static struct quoting_options *filename_quoting_options;
? ls_mode == LS_LS && stdout_isatty ()
: hide_control_chars_opt);
+ qmark_newline = hide_control_chars_opt != false && eolbyte == '\n'
+ && ((line_length
+ && (format == with_commas
+ || format == many_per_line || format == horizontal))
+ || format == one_per_line || format == long_format);
+
int qs = quoting_style_opt;
if (qs < 0)
qs = getenv_quoting_style ();
bool quoted;
enum quoting_style qs = get_quoting_style (options);
- bool needs_further_quoting = qmark_funny_chars
- && (qs == shell_quoting_style
- || qs == shell_always_quoting_style
- || qs == literal_quoting_style);
+ bool partial_quoting = qs == shell_quoting_style
+ || qs == shell_always_quoting_style
+ || qs == literal_quoting_style;
+ bool needs_further_quoting = qmark_funny_chars && partial_quoting;
+
+ /* Note we provide extra protection only for newline separators.
+ I.e., we don't bother protecting "double space" or "comma space"
+ separated entries, as for interactive output, shell escape quoting
+ is best to disambiguate, and for programmatic, --zero is best.
+ Extra processing to mark ', ' and ' ' would be overkill
+ as interactively there will still be ambiguities with multi-byte spaces,
+ and marked characters impact programmatic processing anyway. */
+ bool extra_newline_protection = (!needs_further_quoting
+ && qmark_newline && partial_quoting
+ && !dired && strchr (name, '\n'));
if (needs_general_quoting != 0)
{
quoted = (*name != *buf) || strlen (name) != len;
}
- else if (needs_further_quoting)
+ else if (needs_further_quoting || extra_newline_protection)
{
len = strlen (name);
if (bufsize <= len)
quoted = false;
}
+ /* It's safe to replace newlines without multi-byte iteration, as newlines
+ do not appear as part of any multi-byte encoded character. */
+ if (extra_newline_protection)
+ for (char *p = buf; p < buf + len; p++)
+ if (eolbyte && *p == '\n')
+ *p = '?';
+
if (needs_further_quoting)
{
if (MB_CUR_MAX > 1)
index=$(($index + 1))
done
+# Ensure literal quoting preserves newlines for dired consumers.
+newline='n
+l'
+mkdir newline-dir || framework_failure_
+touch "newline-dir/$newline" || framework_failure_
+ls -l --dired --quoting-style=literal newline-dir > out || fail=1
+set -- $(sed -n 's|^//DIRED// ||p' out)
+test "$#" -eq 2 || framework_failure_
+dd bs=1 skip="$1" count="$(($2 - $1))" < out > actual 2>/dev/null \
+ || framework_failure_
+printf %s "$newline" > exp || framework_failure_
+compare exp actual || fail=1
+
Exit $fail
done > out
compare exp out || fail=1
+# Newlines are preserved with unlimited width, where -m does not wrap,
+newline='n
+l'
+touch "$newline" || framework_failure_
+printf '%s\n' "$newline" > exp || framework_failure_
+ls -m -w0 "$newline" > out || fail=1
+compare exp out || fail=1
+
+# Otherwise protect newlines that could be confused with separators.
+printf '%s\n' 'n?l' > exp || framework_failure_
+ls -m "$newline" > out || fail=1
+compare exp out || fail=1
+
Exit $fail