lib/readline/histfile.c
- history_write_slow: a fallback function that uses stdio to write the
history list to a supplied file descriptor
- - history_do_write: call history_write_slow if ftrucate/mmap/malloc
+ - history_do_write: call history_write_slow if ftruncate/mmap/malloc
fail; hope the simpler approach works
1/27
subst.c
- use locale_mb_cur_max instead of MB_CUR_MAX consistently
+
+ 2/1
+ ---
+builtins/printf.def
+ - printf_builtin: %q and %Q use the `alternate form' flag to force
+ single quoting instead of backslash quoting. $'...' is still
+ preferred if there are characters that require it
+ - getwidestr, getwidechar: functions to take a possibly multibyte
+ character string and convert to a wide character string or a wide
+ character, respectively
+ - convwidestr, convwidechar: functions to take a wide character string
+ or single character, apply any precision, convert back to a multibyte
+ character string, and return the result for printing
+ - printf_builtin: consistently print an error message if printstr()
+ returns < 0, since it will only do that if ferror(stdout) is true;
+ let the PRETURN macro do that if appropriate, so we don't duplicate
+ code
+
+ 2/2
+ ---
+builtins/printf.def
+ - printwidestr: wide-character version of printstr; understands
+ fieldwidth and precision as characters instead of bytes
+ - printf_builtin: interpret %ls and %lc as referring to wide strings
+ and characters, respectively; use printwidestr to print the wide-
+ character string obtained from the (presumably multibyte) argument.
+ Fieldwidth and precision are characters, not bytes
+
+ 2/3
+ ---
+builtins/set.def
+ - unset_builtin: since tokenize_array_reference modifies its NAME
+ argument, we need to restore the original word if there is no
+ array variable found with that name, so we can do ahead and try to
+ unset a function with that wonky name. Inspired by a report from
+ Emanuele Torre <torreemanuele6@gmail.com>
+
+parse.y
+ - read_token_word: don't set the W_HASDOLLAR flag in the returned WORD
+ for <(command) and >(command). That way they can be used in function
+ names.
+
+execute_cmd.c
+ - execute_simple_command: if in posix mode, don't perform function
+ lookup for command names that contain a slash
+
+builtins/enable.def
+ - dyn_load_builtins: don't allow dynamically loaded builtins to have
+ slashes in their name
+
+ 2/4
+ ---
+general.c
+ - valid_function_word: new function, used to check whether a word can
+ be used as a function name. Replaces call to check_identifier();
+ incorporates POSIX interp 383 check for special builtin so we can
+ move it out of execute_intern_function().
+ - valid_function_name: use the FLAGS argument instead of POSIXLY_CORRECT;
+ only reject reserved words if FLAGS&1
+
+execute_cmd.c
+ - execute_intern_function: call valid_function_word() instead of
+ check_identifier(); remove check for special builtin that
+ valid_function_word performs
+
+print_cmd.c
+ - print_function_def,named_function_string: pass POSIXLY_CORRECT to
+ valid_function_name as the FLAGS argument
tests/intl1.sub f
tests/intl2.sub f
tests/intl3.sub f
+tests/intl4.sub f
tests/intl.right f
tests/iquote.tests f
tests/iquote.right f
AC_CHECK_FUNC(wcsdup, AC_DEFINE(HAVE_WCSDUP))
AC_CHECK_FUNC(wcwidth, AC_DEFINE(HAVE_WCWIDTH))
AC_CHECK_FUNC(wctype, AC_DEFINE(HAVE_WCTYPE))
+AC_CHECK_FUNC(wcsnrtombs, AC_DEFINE(HAVE_WCSNRTOMBS))
AC_REPLACE_FUNCS(wcswidth)
for (replaced = 0, new = 0; list; list = list->next)
{
name = list->word->word;
+ if (absolute_program (name))
+ {
+ builtin_error (_("%s: builtin names may not contain slashes"), name);
+ continue;
+ }
size = strlen (name);
struct_name = (char *)xmalloc (size + 8);
This file is printf.def, from which is created printf.c.
It implements the builtin "printf" in Bash.
-Copyright (C) 1997-2022 Free Software Foundation, Inc.
+Copyright (C) 1997-2023 Free Software Foundation, Inc.
This file is part of GNU Bash, the Bourne Again SHell.
#if defined (HANDLE_MULTIBYTE)
static wchar_t *getwidestr (size_t *);
static wint_t getwidechar (void);
+static char *convwidestr (wchar_t *, int);
+static char *convwidechar (wint_t, int);
+static int printwidestr (char *, wchar_t *, size_t, int, int);
#endif
static WORD_LIST *garglist, *orig_arglist;
static char *conv_buf;
static size_t conv_bufsize;
+static inline int
+decodeprec (char *ps)
+{
+ int mpr;
+
+ mpr = *ps++ - '0';
+ while (DIGIT (*ps))
+ mpr = (mpr * 10) + (*ps++ - '0');
+ return mpr;
+}
+
int
printf_builtin (WORD_LIST *list)
{
int ch, fieldwidth, precision;
- int have_fieldwidth, have_precision, use_Lmod, altform;
+ int have_fieldwidth, have_precision, use_Lmod, altform, longform;
char convch, thisch, nextch, *format, *modstart, *precstart, *fmt, *start;
#if defined (HANDLE_MULTIBYTE)
char mbch[25]; /* 25 > MB_LEN_MAX, plus can handle 4-byte UTF-8 and large Unicode characters*/
for (fmt = format; *fmt; fmt++)
{
precision = fieldwidth = 0;
- have_fieldwidth = have_precision = altform = 0;
+ have_fieldwidth = have_precision = altform = longform = 0;
precstart = 0;
if (*fmt == '\\')
while (*fmt && strchr (LENMODS, *fmt))
{
use_Lmod |= USE_LONG_DOUBLE && *fmt == 'L';
+ longform |= *fmt == 'l';
fmt++;
}
{
char p;
+#if defined (HANDLE_MULTIBYTE)
+ if (longform)
+ {
+ wchar_t wc, ws[2];
+ int r;
+
+ wc = getwidechar ();
+ ws[0] = wc;
+ ws[1] = L'\0';
+
+ r = printwidestr (start, ws, 1, fieldwidth, precision);
+ if (r < 0)
+ PRETURN (EXECUTION_FAILURE);
+ break;
+ }
+#endif
p = getchr ();
PF(start, p);
break;
case 's':
{
char *p;
+#if defined (HANDLE_MULTIBYTE)
+ if (longform)
+ {
+ wchar_t *wp;
+ size_t slen;
+ int r;
+ wp = getwidestr (&slen);
+ r = printwidestr (start, wp, slen, fieldwidth, precision);
+ free (wp);
+ if (r < 0)
+ PRETURN (EXECUTION_FAILURE);
+ break;
+ }
+#endif
p = getstr ();
PF(start, p);
break;
modstart[1] = '\0';
r = printstr (start, timebuf, strlen (timebuf), fieldwidth, precision); /* XXX - %s for now */
if (r < 0)
- {
- if (ferror (stdout) == 0)
- {
- sh_wrerror ();
- clearerr (stdout);
- }
- PRETURN (EXECUTION_FAILURE);
- }
+ PRETURN (EXECUTION_FAILURE);
break;
}
in XP -- printf does not handle that well. */
r = printstr (start, xp, rlen, fieldwidth, precision);
if (r < 0)
- {
- if (ferror (stdout) == 0)
- {
- sh_wrerror ();
- clearerr (stdout);
- }
- retval = EXECUTION_FAILURE;
- }
+ retval = EXECUTION_FAILURE;
free (xp);
}
/* Decode precision and apply it to the unquoted string. */
if (convch == 'Q' && precstart)
{
- mpr = *precstart++ - '0';
- while (DIGIT (*precstart))
- mpr = (mpr * 10) + (*precstart++ - '0');
+ mpr = decodeprec (precstart);
/* Error if precision > INT_MAX here? */
precision = (mpr < 0 || mpr > INT_MAX) ? INT_MAX : mpr;
slen = strlen (p);
xp = savestring ("''");
else if (ansic_shouldquote (p))
xp = ansic_quote (p, 0, (int *)0);
+ else if (altform)
+ xp = sh_single_quote (p);
else
xp = sh_backslash_quote (p, 0, 3);
if (xp)
{
+ slen = strlen (xp);
if (convch == 'Q')
{
- slen = strlen (xp);
if (slen > precision)
precision = slen;
}
/* Use printstr to get fieldwidth and precision right. */
- r = printstr (start, xp, strlen (xp), fieldwidth, precision);
- if (r < 0)
- {
- sh_wrerror ();
- clearerr (stdout);
- }
+ r = printstr (start, xp, slen, fieldwidth, precision);
+ /* Let PRETURN print the error message. */
free (xp);
}
STRING: expanded string argument
LEN: length of expanded string
FIELDWIDTH: argument for width of `*'
- PRECISION: argument for precision of `*' */
+ PRECISION: argument for precision of `*'
+
+ Returns -1 on detectable write error, 0 otherwise. */
+
static int
printstr (char *fmt, char *string, int len, int fieldwidth, int precision)
{
return (ferror (stdout) ? -1 : 0);
}
+
+#if defined (HANDLE_MULTIBYTE)
+/* A wide-character version of printstr */
+static int
+printwidestr (char *fmt, wchar_t *wstring, size_t len, int fieldwidth, int precision)
+{
+#if 0
+ char *s;
+#endif
+ char *string;
+ int padlen, nc, ljust, i;
+ int fw, pr; /* fieldwidth and precision */
+ intmax_t mfw, mpr;
+
+ if (wstring == 0)
+ wstring = L"";
+
+#if 0
+ s = fmt;
+#endif
+ if (*fmt == '%')
+ fmt++;
+
+ ljust = fw = 0;
+ pr = -1;
+ mfw = 0;
+ mpr = -1;
+
+ /* skip flags */
+ while (strchr (SKIP1, *fmt))
+ {
+ if (*fmt == '-')
+ ljust = 1;
+ fmt++;
+ }
+
+ /* get fieldwidth, if present. rely on caller to clamp fieldwidth at INT_MAX */
+ if (*fmt == '*')
+ {
+ fmt++;
+ fw = fieldwidth;
+ if (fw < 0)
+ {
+ fw = -fw;
+ ljust = 1;
+ }
+ }
+ else if (DIGIT (*fmt))
+ {
+ mfw = *fmt++ - '0';
+ while (DIGIT (*fmt))
+ mfw = (mfw * 10) + (*fmt++ - '0');
+ /* Error if fieldwidth > INT_MAX here? */
+ fw = (mfw < 0 || mfw > INT_MAX) ? INT_MAX : mfw;
+ }
+
+ /* get precision, if present. doesn't handle negative precisions */
+ if (*fmt == '.')
+ {
+ fmt++;
+ if (*fmt == '*')
+ {
+ fmt++;
+ pr = precision;
+ }
+ else if (DIGIT (*fmt))
+ {
+ mpr = *fmt++ - '0';
+ while (DIGIT (*fmt))
+ mpr = (mpr * 10) + (*fmt++ - '0');
+ /* Error if precision > INT_MAX here? */
+ pr = (mpr < 0 || mpr > INT_MAX) ? INT_MAX : mpr;
+ if (pr < precision && precision < INT_MAX)
+ pr = precision; /* XXX */
+ }
+ else
+ pr = 0; /* "a null digit string is treated as zero" */
+ }
+
+ /* chars from wide string to print */
+ nc = (pr >= 0 && pr <= len) ? pr : len;
+
+ padlen = fw - nc;
+ if (padlen < 0)
+ padlen = 0;
+ if (ljust)
+ padlen = -padlen;
+
+ /* leading pad characters */
+ for (; padlen > 0; padlen--)
+ PC (' ');
+
+ /* convert WSTRING to multibyte character STRING, honoring PRECISION */
+ string = convwidestr (wstring, pr);
+
+ /* output STRING, assuming that convwidestr has taken care of the precision
+ and returned only the necessary bytes. */
+ for (i = 0; string[i]; i++)
+ PC (string[i]);
+
+ /* output any necessary trailing padding */
+ for (; padlen < 0; padlen++)
+ PC (' ');
+
+ free (string);
+ return (ferror (stdout) ? -1 : 0);
+}
+#endif
/* Convert STRING by expanding the escape sequences specified by the
POSIX standard for printf's `%b' format string. If SAWC is non-null,
garglist = garglist->next;
return (wc);
}
+
+/* The basic approach is to take the wide character string, apply any
+ precision in terms of characters (otherwise the precision is useless),
+ compute the size of the output buffer, call wcsrtombs to convert back
+ to multibyte characters, and return the result. */
+static char *
+convwidestr (wchar_t *ws, int prec)
+{
+ const wchar_t *ts;
+ wchar_t wc;
+ char *ret;
+ size_t rlen, rsize;
+ DECLARE_MBSTATE;
+
+ ts = (const wchar_t *)ws;
+
+ if (prec > 0)
+ {
+ rsize = prec * MB_CUR_MAX;
+ ret = (char *)xmalloc (rsize + 1);
+#if defined (HAVE_WCSNRTOMBS)
+ rlen = wcsnrtombs (ret, &ts, prec, rsize, &state);
+#else
+ wc = ws[prec];
+ ws[prec] = L'\0';
+ rlen = wcsrtombs (ret, &ts, rsize, &state);
+ ws[prec] = wc;
+#endif
+ }
+ else
+ {
+ rlen = wcsrtombs (NULL, &ts, 0, &state);
+ if (rlen != (size_t)-1)
+ {
+ memset (&state, '\0', sizeof (mbstate_t));
+ ret = (char *)xmalloc (rlen + 1);
+ rlen = wcsrtombs (ret, &ts, rlen, &state);
+ }
+ else
+ ret = (char *)xmalloc (1);
+ }
+ if (MB_INVALIDCH (rlen))
+ rlen = 0;
+
+ ret[rlen] = '\0';
+ return ret;
+}
+
+static char *
+convwidechar (wint_t wi, int prec)
+{
+ wchar_t wc;
+ char *ret;
+ size_t rlen;
+ DECLARE_MBSTATE;
+
+ wc = (wchar_t)wi;
+ ret = (char *)xmalloc (MB_LEN_MAX + 1);
+ rlen = wcrtomb (ret, wc, &state);
+ if (MB_INVALIDCH (rlen))
+ rlen = 0;
+
+ ret[rlen] = '\0';
+ return ret;
+}
#endif
#define NEXT_VARIABLE() any_failed++; list = list->next; continue;
+#define RESTORE_NAME() \
+do { \
+ tname = name + strlen (name); \
+ if (tname == t - 1) /* probably a paranoid check */ \
+ { \
+ tname[0] = '['; \
+ t[strlen (t)] = ']'; \
+ } \
+} while (0)
+
int
unset_builtin (WORD_LIST *list)
{
#if defined (ARRAY_VARS)
vflags = builtin_arrayref_flags (list->word, base_vflags);
-#endif
-
-#if defined (ARRAY_VARS)
unset_array = 0;
/* XXX valid array reference second arg was 0 */
- if (!unset_function && nameref == 0 && tokenize_array_reference (name, vflags, &t))
+ /* XXX tokenize_array_reference modifies NAME if it succeeds */
+ if (unset_function == 0 && nameref == 0 && tokenize_array_reference (name, vflags, &t))
unset_array = 1;
#endif
+
/* Get error checking out of the way first. The low-level functions
just perform the unset, relying on the caller to verify. */
valid_id = legal_identifier (name);
treat them as potential shell function names. */
if (global_unset_func == 0 && global_unset_var == 0 && valid_id == 0)
{
+#if defined (ARRAY_VARS)
+ if (unset_array)
+ RESTORE_NAME ();
+#endif
unset_variable = unset_array = 0;
unset_function = 1;
}
find a function after unsuccessfully searching for a variable,
note that we're acting on a function now as if -f were
supplied. The readonly check below takes care of it. */
- if (var == 0 && nameref == 0 && unset_variable == 0 && unset_function == 0)
+ if (var == 0 && nameref == 0 && unset_variable == 0 && unset_function == 0)
{
+#if defined (ARRAY_VARS)
+ /* We modified NAME in the call to tokenize_array_reference, so we
+ need to restore it here. We turned the original `[' and `]' into
+ NULL, to isolate the array name and subscript. This only happens
+ if tokenize_array_reference succeeds with a non-NULL subscript
+ pointer, and UNSET_ARRAY is set to 1 only in this case. */
+ if (unset_array)
+ {
+ RESTORE_NAME();
+ unset_array = 0;
+ }
+#endif
if (var = find_function (name))
unset_function = 1;
}
/* Define if you have the wcsdup function. */
#undef HAVE_WCSDUP
+/* Define if you have the wcsnrtombs function. */
+#undef HAVE_WCSNRTOMBS
+
/* Define if you have the wctype function. */
#undef HAVE_WCTYPE
#! /bin/sh
-# From configure.ac for Bash 5.2, version 5.047.
+# From configure.ac for Bash 5.2, version 5.048.
# Guess values for system-dependent variables and create Makefiles.
# Generated by GNU Autoconf 2.71 for bash 5.2-maint.
#
fi
+ac_fn_c_check_func "$LINENO" "wcsnrtombs" "ac_cv_func_wcsnrtombs"
+if test "x$ac_cv_func_wcsnrtombs" = xyes
+then :
+ printf "%s\n" "#define HAVE_WCSNRTOMBS 1" >>confdefs.h
+
+fi
+
ac_fn_c_check_func "$LINENO" "wcswidth" "ac_cv_func_wcswidth"
if test "x$ac_cv_func_wcswidth" = xyes
dnl
dnl Process this file with autoconf to produce a configure script.
-# Copyright (C) 1987-2022 Free Software Foundation, Inc.
+# Copyright (C) 1987-2023 Free Software Foundation, Inc.
#
# This program is free software: you can redistribute it and/or modify
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
-AC_REVISION([for Bash 5.2, version 5.047])dnl
+AC_REVISION([for Bash 5.2, version 5.048])dnl
define(bashvers, 5.2)
define(relstatus, maint)
Function names may not be the same as one of the @sc{posix} special
builtins.
+@item
+Even if a shell function whose name contains a slash was defined before
+entering @sc{posix} mode, the shell will not execute a function whose name
+contains one or more slashes.
+
@item
@sc{posix} special builtins are found before shell functions
during command lookup.
builtin_is_special = 1;
}
if (builtin == 0)
- func = find_function (words->word->word);
+ func = (posixly_correct == 0 || absolute_program (words->word->word) == 0) ? find_function (words->word->word) : 0;
}
/* What happens in posix mode when an assignment preceding a command name
SHELL_VAR *var;
char *t;
- if (check_identifier (name, posixly_correct) == 0)
+ if (valid_function_word (name, posixly_correct) == 0)
{
- if (posixly_correct && interactive_shell == 0)
+ if (posixly_correct)
{
last_command_exit_value = EX_BADUSAGE;
- jump_to_top_level (ERREXIT);
+ jump_to_top_level (interactive_shell ? DISCARD : ERREXIT);
}
return (EXECUTION_FAILURE);
}
name->word = t;
}
- /* Posix interpretation 383 */
- if (posixly_correct && find_special_builtin (name->word))
- {
- internal_error (_("`%s': is a special builtin"), name->word);
- last_command_exit_value = EX_BADUSAGE;
- jump_to_top_level (interactive_shell ? DISCARD : ERREXIT);
- }
-
var = find_function (name->word);
if (var && (readonly_p (var) || noassign_p (var)))
{
}
/* Return 1 if this is a valid identifer that can be used to declare a function
- without the `function' reserved word. FLAGS is currently unused; a
- placeholder for the future. */
+ without the `function' reserved word. FLAGS&1 means to reject POSIX
+ non-identifiers and reserved words; FLAGS&2 means to suppress the check
+ for ASSIGNMENT_WORD. So you can pass posixly_correct as FLAGS and get
+ POSIX checks. */
int
valid_function_name (const char *name, int flags)
{
- if (find_reserved_word (name) >= 0)
+ if ((flags & 1) && find_reserved_word (name) >= 0)
return 0;
- if (posixly_correct && (all_digits (name) || legal_identifier (name) == 0))
+ if ((flags & 1) && (all_digits (name) || legal_identifier (name) == 0))
return 0;
- if (assignment (name, 0)) /* difference between WORD and ASSIGNMENT_WORD */
+ /* pass flags & 2 to suppress this check */
+ if ((flags & 2) == 0 && assignment (name, 0)) /* difference between WORD and ASSIGNMENT_WORD */
return 0;
return 1;
}
+/* Return 1 if this is an identifier that can be used as a function name
+ when declaring a function. We don't allow `$' for historical reasons.
+ We allow quotes (for now), slashes, and pretty much everything else.
+ If FLAGS is non-zero (it's usually posixly_correct), we check the name
+ for additional posix restrictions using valid_function_name(). We pass
+ flags|2 to valid_function_name to suppress the check for an assignment
+ word, since we want to allow those here. */
+int
+valid_function_word (WORD_DESC *word, int flags)
+{
+ char *name;
+
+ name = word->word;
+ if ((word->flags & W_HASDOLLAR)) /* allow quotes for now */
+ {
+ internal_error (_("`%s': not a valid identifier"), name);
+ return (0);
+ }
+ /* POSIX interpretation 383 */
+ if (flags && find_special_builtin (name))
+ {
+ internal_error (_("`%s': is a special builtin"), name);
+ return (0);
+ }
+ if (flags && valid_function_name (name, flags|2) == 0)
+ {
+ internal_error (_("`%s': not a valid identifier"), name);
+ return (0);
+ }
+ return 1;
+}
+
/* Returns non-zero if STRING is an assignment statement. The returned value
is the index of the `=' sign. If FLAGS&1 we are expecting a compound assignment
and require an array subscript before the `=' to denote an assignment
extern int check_selfref (const char *, char *, int);
extern int legal_alias_name (const char *, int);
extern int valid_function_name (const char *, int);
+extern int valid_function_word (WORD_DESC *, int);
extern int line_isblank (const char *);
extern int assignment (const char *, int);
strcpy (token + token_index, ttok);
token_index += ttoklen;
FREE (ttok);
- dollar_present = 1;
+ dollar_present |= character == '$';
all_digit_token = 0;
goto next_character;
}
`function' to words that are not valid POSIX identifiers. */
if (posixly_correct == 0)
cprintf ("function %s () \n", func->name->word);
- else if (valid_function_name (func->name->word, 0) == 0)
+ else if (valid_function_name (func->name->word, posixly_correct) == 0)
cprintf ("function %s () \n", func->name->word);
else
cprintf ("%s () \n", func->name->word);
+
+ begin_unwind_frame ("function-def");
add_unwind_protect (reset_locals, 0);
indent (indentation);
was_heredoc = 0; /* not printing any here-documents now */
}
- remove_unwind_protect (); /* unwind_protect_pointer */
- remove_unwind_protect (); /* reset_locals */
+ discard_unwind_frame ("function-def");
}
/* Return the string representation of the named function.
if (name && *name)
{
- if (valid_function_name (name, 0) == 0)
+ if (valid_function_name (name, posixly_correct) == 0)
cprintf ("function ");
cprintf ("%s ", name);
}
$'5\247@3\231+\306S8\237\242\352\263'
+ : $'5\247@3\231+\306S8\237\242\352\263'
+ set +x
+ಇಳಿಕೆಗಳು
+ಇಳ
+ಇ
+ಇಳ
+ ಇಳ
+ಇಳ ---
+ಇ
+ಇ
+ ಇ
+ಇ ---
${THIS_SH} ./unicode2.sub
${THIS_SH} ./unicode3.sub 2>&1
+
+${THIS_SH} ./intl4.sub
--- /dev/null
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+# printf "%ls" and "%lc" format specifiers for multibyte characters with
+# field width and precision in characters instead of bytes
+LC_ALL=en_US.UTF-8
+
+V=ಇಳಿಕೆಗಳು
+V2=${V:0:2}
+V3=${V:0:1}
+
+printf "%ls\n" "$V"
+printf "%ls\n" "$V2"
+printf "%lc\n" "$V"
+printf "%.2ls\n" "$V"
+
+printf "%4.2ls\n" "$V"
+printf "%-4.2ls---\n" "$V"
+
+printf "%ls\n" "$V3"
+printf "%lc\n" "$V3"
+
+printf "%4.2lc\n" "$V3"
+printf "%-4.2lc---\n" "$V3"
# tests variable assignment with -v
${THIS_SH} ./printf1.sub
-
${THIS_SH} ./printf2.sub
-
${THIS_SH} ./printf3.sub
-
${THIS_SH} ./printf4.sub