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