1 #!/usr/bin/env bash
2 #
3 # printf
4 # bash-completion uses this odd printf -v construction. It seems to mostly use
5 # %s and %q though.
6 #
7 # %s should just be
8 # declare $var='val'
9 #
10 # NOTE:
11 # /usr/bin/printf %q "'" seems wrong.
12 # $ /usr/bin/printf %q "'"
13 # ''\'''
14 #
15 # I suppose it is technically correct, but it looks very ugly.
16
17 #### printf with no args
18 printf
19 ## status: 2
20 ## OK dash/mksh/zsh status: 1
21 ## stdout-json: ""
22
23 #### printf -v %s
24 var=foo
25 printf -v $var %s 'hello there'
26 argv.py "$foo"
27 ## STDOUT:
28 ['hello there']
29 ## END
30 ## N-I mksh/zsh/ash STDOUT:
31 -v['']
32 ## END
33 ## N-I dash STDOUT:
34 ['']
35 ## END
36
37 #### printf -v %q
38 val='"quoted" with spaces and \'
39
40 # quote 'val' and store it in foo
41 printf -v foo %q "$val"
42 # then round trip back to eval
43 eval "bar=$foo"
44
45 # debugging:
46 #echo foo="$foo"
47 #echo bar="$bar"
48 #echo val="$val"
49
50 test "$bar" = "$val" && echo OK
51 ## STDOUT:
52 OK
53 ## END
54 ## N-I mksh/zsh/ash stdout-json: "-v"
55 ## N-I mksh/zsh/ash status: 1
56 ## N-I dash stdout-json: ""
57 ## N-I dash status: 1
58
59 #### printf -v a[1]
60 a=(a b c)
61 printf -v 'a[1]' %s 'foo'
62 echo status=$?
63 argv.py "${a[@]}"
64 ## STDOUT:
65 status=0
66 ['a', 'foo', 'c']
67 ## END
68 ## N-I mksh/zsh STDOUT:
69 -vstatus=0
70 ['a', 'b', 'c']
71 ## END
72 ## N-I dash/ash stdout-json: ""
73 ## N-I dash/ash status: 2
74 ## N-I osh STDOUT:
75 status=2
76 ['a', 'b', 'c']
77 ## END
78
79 #### dynamic declare instead of %s
80 var=foo
81 declare $var='hello there'
82 argv.py "$foo"
83 ## STDOUT:
84 ['hello there']
85 ## END
86 ## N-I dash/mksh/ash STDOUT:
87 ['']
88 ## END
89
90 #### dynamic declare instead of %q
91 var=foo
92 val='"quoted" with spaces and \'
93 # I think this is bash 4.4 only.
94 declare $var="${val@Q}"
95 echo "$foo"
96 ## STDOUT:
97 '"quoted" with spaces and \'
98 ## END
99 ## N-I dash/ash stdout-json: ""
100 ## N-I dash/ash status: 2
101 ## N-I mksh stdout-json: "\n"
102 ## N-I zsh stdout-json: ""
103 ## N-I zsh status: 1
104
105 #### printf -v dynamic scope
106 case $SH in *mksh|*zsh|*dash|*/ash) echo not implemented; exit ;; esac
107 # OK so printf is like assigning to a var.
108 # printf -v foo %q "$bar" is like
109 # foo=${bar@Q}
110 dollar='dollar'
111 f() {
112 local mylocal=foo
113 printf -v dollar %q '$' # assign foo to a quoted dollar
114 printf -v mylocal %q 'mylocal'
115 echo dollar=$dollar
116 echo mylocal=$mylocal
117 }
118 echo dollar=$dollar
119 echo --
120 f
121 echo --
122 echo dollar=$dollar
123 echo mylocal=$mylocal
124 ## STDOUT:
125 dollar=dollar
126 --
127 dollar=\$
128 mylocal=mylocal
129 --
130 dollar=\$
131 mylocal=
132 ## END
133 ## OK osh STDOUT:
134 dollar=dollar
135 --
136 dollar='$'
137 mylocal='mylocal'
138 --
139 dollar='$'
140 mylocal=
141 ## END
142 ## N-I dash/ash/mksh/zsh STDOUT:
143 not implemented
144 ## END
145
146 #### printf with too few arguments
147 printf -- '-%s-%s-%s-\n' 'a b' 'x y'
148 ## STDOUT:
149 -a b-x y--
150 ## END
151
152 #### printf with too many arguments
153 printf -- '-%s-%s-\n' a b c d e
154 ## STDOUT:
155 -a-b-
156 -c-d-
157 -e--
158 ## END
159
160 #### printf width strings
161 printf '[%5s]\n' abc
162 printf '[%-5s]\n' abc
163 ## STDOUT:
164 [ abc]
165 [abc ]
166 ## END
167
168 #### printf integer
169 printf '%d\n' 42
170 printf '%i\n' 42 # synonym
171 printf '%d\n' \'a # if first character is a quote, use character code
172 printf '%d\n' \"a # double quotes work too
173 printf '[%5d]\n' 42
174 printf '[%-5d]\n' 42
175 printf '[%05d]\n' 42
176 #printf '[%-05d]\n' 42 # the leading 0 is meaningless
177 #[42 ]
178 ## STDOUT:
179 42
180 42
181 97
182 97
183 [ 42]
184 [42 ]
185 [00042]
186 ## END
187
188 #### printf %6.4d -- precision means something different for integers !?
189 printf '[%6.4d]\n' 42
190 ## STDOUT:
191 [ 0042]
192 ## END
193 ## N-I osh stdout-json: ""
194 ## N-I osh status: 2
195
196 #### printf %6.4s does both truncation and padding
197 printf '[%6s]\n' foo
198 printf '[%6.4s]\n' foo
199 printf '[%-6.4s]\n' foo
200 printf '[%6s]\n' spam-eggs
201 printf '[%6.4s]\n' spam-eggs
202 printf '[%-6.4s]\n' spam-eggs
203 ## STDOUT:
204 [ foo]
205 [ foo]
206 [foo ]
207 [spam-eggs]
208 [ spam]
209 [spam ]
210 ## END
211
212 #### printf %6.0s and %0.0s
213 printf '[%6.0s]\n' foo
214 printf '[%0.0s]\n' foo
215 ## STDOUT:
216 [ ]
217 []
218 ## END
219 ## N-I mksh stdout-json: "[ ]\n["
220 ## N-I mksh status: 1
221
222 #### unsigned / octal / hex
223 printf '[%u]\n' 42
224 printf '[%o]\n' 42
225 printf '[%x]\n' 42
226 printf '[%X]\n' 42
227 printf '[%X]\n' \'a # if first character is a quote, use character code
228 printf '[%X]\n' \'ab # extra chars ignored
229 ## STDOUT:
230 [42]
231 [52]
232 [2a]
233 [2A]
234 [61]
235 [61]
236 ## END
237
238 #### empty string (osh is more strict)
239 printf '%d\n' ''
240 ## OK osh stdout-json: ""
241 ## OK osh status: 1
242 ## OK ash status: 1
243 ## STDOUT:
244 0
245 ## END
246
247 #### No char after ' (osh is more strict)
248
249 # most shells use 0 here
250 printf '%d\n' \'
251
252 ## OK osh stdout-json: ""
253 ## OK osh status: 1
254 ## OK mksh status: 1
255 ## STDOUT:
256 0
257 ## END
258
259 #### Unicode char with ' (osh is more strict)
260
261 # the mu character is U+03BC
262
263 printf '%x\n' \'μ
264
265 ## STDOUT:
266 3bc
267 ## END
268 ## BUG dash/mksh/ash STDOUT:
269 ce
270 ## END
271
272 #### negative numbers with unsigned / octal / hex
273 printf '[%u]\n' -42
274 printf '[%o]\n' -42
275 printf '[%x]\n' -42
276 printf '[%X]\n' -42
277 ## STDOUT:
278 [18446744073709551574]
279 [1777777777777777777726]
280 [ffffffffffffffd6]
281 [FFFFFFFFFFFFFFD6]
282 ## END
283
284 # osh DISALLOWS this because the output depends on the machine architecture.
285 ## N-I osh stdout-json: ""
286 ## N-I osh status: 1
287
288 #### printf floating point (not required, but they all implement it)
289 printf '[%f]\n' 3.14159
290 printf '[%.2f]\n' 3.14159
291 printf '[%8.2f]\n' 3.14159
292 printf '[%-8.2f]\n' 3.14159
293 printf '[%-f]\n' 3.14159
294 printf '[%-f]\n' 3.14
295 ## STDOUT:
296 [3.141590]
297 [3.14]
298 [ 3.14]
299 [3.14 ]
300 [3.141590]
301 [3.140000]
302 ## END
303 ## N-I osh stdout-json: ""
304 ## N-I osh status: 2
305
306 #### printf floating point with - and 0
307 printf '[%8.4f]\n' 3.14
308 printf '[%08.4f]\n' 3.14
309 printf '[%8.04f]\n' 3.14 # meaning less 0
310 printf '[%08.04f]\n' 3.14
311 echo ---
312 # these all boil down to the same thing. The -, 8, and 4 are respected, but
313 # none of the 0 are.
314 printf '[%-8.4f]\n' 3.14
315 printf '[%-08.4f]\n' 3.14
316 printf '[%-8.04f]\n' 3.14
317 printf '[%-08.04f]\n' 3.14
318 ## STDOUT:
319 [ 3.1400]
320 [003.1400]
321 [ 3.1400]
322 [003.1400]
323 ---
324 [3.1400 ]
325 [3.1400 ]
326 [3.1400 ]
327 [3.1400 ]
328 ## END
329 ## N-I osh STDOUT:
330 ---
331 ## END
332 ## N-I osh status: 2
333
334 #### printf eE fF gG
335 printf '[%e]\n' 3.14
336 printf '[%E]\n' 3.14
337 printf '[%f]\n' 3.14
338 # bash is the only one that implements %F? Is it a synonym?
339 #printf '[%F]\n' 3.14
340 printf '[%g]\n' 3.14
341 printf '[%G]\n' 3.14
342 ## STDOUT:
343 [3.140000e+00]
344 [3.140000E+00]
345 [3.140000]
346 [3.14]
347 [3.14]
348 ## END
349 ## N-I osh stdout-json: ""
350 ## N-I osh status: 2
351
352 #### printf backslash escapes
353 argv.py "$(printf 'a\tb')"
354 argv.py "$(printf '\xE2\x98\xA0')"
355 argv.py "$(printf '\044e')"
356 argv.py "$(printf '\0377')" # out of range
357 ## STDOUT:
358 ['a\tb']
359 ['\xe2\x98\xa0']
360 ['$e']
361 ['\x1f7']
362 ## END
363 ## N-I dash STDOUT:
364 ['a\tb']
365 ['\\xE2\\x98\\xA0']
366 ['$e']
367 ['\x1f7']
368 ## END
369
370 #### printf octal backslash escapes
371 argv.py "$(printf '\0377')"
372 argv.py "$(printf '\377')"
373 ## STDOUT:
374 ['\x1f7']
375 ['\xff']
376 ## END
377
378 #### printf unicode backslash escapes
379 argv.py "$(printf '\u2620')"
380 argv.py "$(printf '\U0000065f')"
381 ## STDOUT:
382 ['\xe2\x98\xa0']
383 ['\xd9\x9f']
384 ## END
385 ## N-I dash/ash STDOUT:
386 ['\\u2620']
387 ['\\U0000065f']
388 ## END
389
390 #### printf invalid backslash escape (is ignored)
391 printf '[\Z]\n'
392 ## STDOUT:
393 [\Z]
394 ## END
395
396 #### printf % escapes
397 printf '[%%]\n'
398 ## STDOUT:
399 [%]
400 ## END
401
402 #### printf %b backslash escaping
403 printf '[%s]\n' '\044' # escapes not evaluated
404 printf '[%b]\n' '\044' # YES, escapes evaluated
405 echo status=$?
406 ## STDOUT:
407 [\044]
408 [$]
409 status=0
410 ## END
411 ## N-I osh STDOUT:
412 [\044]
413 status=2
414 ## END
415
416 #### printf %c -- doesn't respect UTF-8! Bad.
417 twomu=$'\u03bc\u03bc'
418 printf '[%s]\n' "$twomu"
419 printf '%c' "$twomu" | wc --bytes
420 ## STDOUT:
421 [μμ]
422 1
423 ## END
424 ## N-I dash STDOUT:
425 [$\u03bc\u03bc]
426 1
427 ## END
428 ## N-I ash STDOUT:
429 [\u03bc\u03bc]
430 1
431 ## END
432 ## N-I osh STDOUT:
433 [μμ]
434 0
435 ## END
436
437 #### printf invalid format
438 printf '%z' 42
439 echo status=$?
440 printf '%-z' 42
441 echo status=$?
442 ## STDOUT:
443 status=1
444 status=1
445 ## END
446 # osh emits parse errors
447 ## OK osh STDOUT:
448 status=2
449 status=2
450 ## END
451 ## BUG ash STDOUT:
452 status=0
453 status=0
454 ## END
455
456 #### printf %q
457 x='a b'
458 printf '[%q]\n' "$x"
459 ## STDOUT:
460 ['a b']
461 ## END
462 ## OK bash/zsh STDOUT:
463 [a\ b]
464 ## END
465 ## N-I ash/dash stdout-json: "["
466 ## N-I ash/dash status: 1
467
468 #### printf %6q (width)
469 # NOTE: coreutils /usr/bin/printf does NOT implement this %6q !!!
470 x='a b'
471 printf '[%6q]\n' "$x"
472 ## STDOUT:
473 [ 'a b']
474 ## END
475 ## OK bash/zsh STDOUT:
476 [ a\ b]
477 ## END
478 ## N-I mksh/ash/dash stdout-json: "["
479 ## N-I mksh/ash/dash status: 1
480
481 #### printf + and space flags
482 # I didn't know these existed -- I only knew about - and 0 !
483 printf '[%+d]\n' 42
484 printf '[%+d]\n' -42
485 printf '[% d]\n' 42
486 printf '[% d]\n' -42
487 ## STDOUT:
488 [+42]
489 [-42]
490 [ 42]
491 [-42]
492 ## END
493 ## N-I osh stdout-json: ""
494 ## N-I osh status: 2
495
496 #### printf # flag
497 # I didn't know these existed -- I only knew about - and 0 !
498 printf '[%#o]\n' 42
499 printf '[%#x]\n' 42
500 printf '[%#X]\n' 42
501 echo ---
502 printf '[%#f]\n' 3
503 ## STDOUT:
504 [052]
505 [0x2a]
506 [0X2A]
507 ---
508 [3.000000]
509 ## END
510 ## N-I osh STDOUT:
511 ---
512 ## END
513 ## N-I osh status: 2
514
515 #### Runtime error for invalid integer
516 x=3abc
517 printf '%d\n' $x
518 echo status=$?
519 printf '%d\n' xyz
520 echo status=$?
521 ## STDOUT:
522 3
523 status=1
524 0
525 status=1
526 ## END
527 # zsh should exit 1 in both cases
528 ## BUG zsh STDOUT:
529 0
530 status=1
531 0
532 status=0
533 ## END
534 # fails but also prints 0 instead of 3abc
535 ## BUG ash STDOUT:
536 0
537 status=1
538 0
539 status=1
540 ## END
541 # osh doesn't print anything invalid
542 ## OK osh STDOUT:
543 status=1
544 status=1
545 ## END
546
547 #### %(strftime format)T
548 printf '%(%Y-%m-%d)T\n' 1557978599
549 echo status=$?
550 ## STDOUT:
551 2019-05-15
552 status=0
553 ## END
554 ## N-I dash/mksh/zsh/ash STDOUT:
555 status=1
556 ## END
557 ## N-I osh STDOUT:
558 status=2
559 ## END