logo

utils-std

Collection of commonly available Unix tools git clone https://anongit.hacktivis.me/git/utils-std.git/
commit: 1c81a03d121a969d0dfe9c52f480a66554efd00d
parent 2cb594ab23dbc1cee684ee04401be431444bda69
Author: Haelwenn (lanodan) Monnier <contact@hacktivis.me>
Date:   Fri, 30 May 2025 08:42:17 +0200

cmd/printf: Add support for %q specifier

Diffstat:

Mcmd/printf.111+++++++++++
Mcmd/printf.c61+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest-cmd/printf.sh10+++++++++-
3 files changed, 81 insertions(+), 1 deletion(-)

diff --git a/cmd/printf.1 b/cmd/printf.1 @@ -338,6 +338,13 @@ and that an additional escape sequence stops further output from this .Nm invocation. +.It Cm q +Print +.Ar argument +so it can be reused for shell input, +escaping control characters and single-quote with POSIX.1-2024 +.Cm $'' +(dollar-single-quote) syntax. .It Cm n$ Allows reordering of the output according to .Ar argument . @@ -420,6 +427,10 @@ backslash-escapes are extensions inspired by .Xr sh 1 Ns 's dollar-single-quote($'…') escapes. +.Pp +The +.Cm %q +format specifier is an extension inspired by GNU coreutils. .Sh HISTORY The .Nm diff --git a/cmd/printf.c b/cmd/printf.c @@ -4,6 +4,7 @@ #define _POSIX_C_SOURCE 200809L #include <errno.h> +#include <stdbool.h> #include <stdio.h> // printf #include <stdlib.h> // strtoul, strtod #include <string.h> // strlen, memchr @@ -29,6 +30,12 @@ isxdigit(int c) return isdigit(c) || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f'); } +static int +iscntrl(int c) +{ + return (unsigned)c < 0x20 || c == 0x7f; +} + // len parameter needed because of NULL escapes // returns 1 for handling '\c' early ends static int @@ -520,6 +527,60 @@ main(int argc, char *argv[]) break; } + case 'q': + { + if(fwidth != 0) + { + fprintf( + stderr, + "printf: error: (format position %d) field-width is unsupported with 'q' specifier\n", + (int)(fmt_idx - fmt)); + return 1; + } + + if(precision != -1) + { + fprintf( + stderr, + "printf: error: (format position %d) precision is unsupported with 'q' specifier\n", + (int)(fmt_idx - fmt)); + return 1; + } + + size_t arglen = strlen(fmt_arg); + bool quoted = false; + for(size_t i = 0; i < arglen; i++) + { + if(!(iscntrl(fmt_arg[i]) || fmt_arg[i] == '\'' || fmt_arg[i] == '"')) + { + putchar(fmt_arg[i]); + continue; + } + + if(!quoted) + { + quoted = true; + fputs("$'", stdout); + } + + switch(fmt_arg[i]) + { + default: + case 0x7F: + /* for control chars */ + printf("\\c%c", fmt_arg[i] == 0x7F ? '?' : fmt_arg[i] + '@'); + break; + case '\'': + fputs("\\'", stdout); + break; + case '"': + putchar(fmt_arg[i]); + break; + } + } + if(quoted) putchar('\''); + break; + } case 'c': printf("%*c", fwidth, *fmt_arg); break; diff --git a/test-cmd/printf.sh b/test-cmd/printf.sh @@ -2,7 +2,7 @@ # SPDX-FileCopyrightText: 2017 Haelwenn (lanodan) Monnier <contact+utils@hacktivis.me> # SPDX-License-Identifier: MPL-2.0 -plans=33 +plans=37 WD="$(dirname "$0")/../" target="${WD}/cmd/printf" . "${WD}/test-cmd/tap.sh" @@ -29,6 +29,14 @@ t_args fmt_b ' t_args fmt_b_rightpad '!{} .' '%-6b%c' '\041\x7B\x7d' . t_args fmt_b_leftpad ' !{}.' '%6b%c' '\041\x7B\x7d' . +t_args fmt_q_print 'foo +baré +' '%q\n' foo baré +t_args fmt_q_cntrl "back$'\cHdel\c?nl\cJ'" '%q' 'backdelnl +' +t_args fmt_q_sq "single$'\\'quote'" %q "single'quote" +t_args fmt_q_dq "double$'"'"'"quote'" %q 'double"quote' + t_args fmt_c 'foo' %c f oo oooo t_args fmt_d 10, %d, 10