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