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 |