OILS / spec / builtin-printf.test.sh View on Github | oilshell.org

901 lines, 486 significant
1## oils_failures_allowed: 1
2## compare_shells: dash bash mksh zsh ash
3
4# printf
5# bash-completion uses this odd printf -v construction. It seems to mostly use
6# %s and %q though.
7#
8# %s should just be
9# declare $var='val'
10#
11# NOTE:
12# /usr/bin/printf %q "'" seems wrong.
13# $ /usr/bin/printf %q "'"
14# ''\'''
15#
16# I suppose it is technically correct, but it looks very ugly.
17
18#### printf with no args
19printf
20## status: 2
21## OK mksh/zsh status: 1
22## stdout-json: ""
23
24#### printf -v %s
25var=foo
26printf -v $var %s 'hello there'
27argv.py "$foo"
28## STDOUT:
29['hello there']
30## END
31## N-I mksh/zsh/ash STDOUT:
32-v['']
33## END
34## N-I dash STDOUT:
35['']
36## END
37
38#### printf -v %q
39val='"quoted" with spaces and \'
40
41# quote 'val' and store it in foo
42printf -v foo %q "$val"
43# then round trip back to eval
44eval "bar=$foo"
45
46# debugging:
47#echo foo="$foo"
48#echo bar="$bar"
49#echo val="$val"
50
51test "$bar" = "$val" && echo OK
52## STDOUT:
53OK
54## END
55## N-I mksh/zsh/ash stdout-json: "-v"
56## N-I mksh/zsh/ash status: 1
57## N-I dash stdout-json: ""
58## N-I dash status: 1
59
60#### printf -v a[1]
61a=(a b c)
62printf -v 'a[1]' %s 'foo'
63echo status=$?
64argv.py "${a[@]}"
65## STDOUT:
66status=0
67['a', 'foo', 'c']
68## END
69## N-I mksh/zsh STDOUT:
70-vstatus=0
71['a', 'b', 'c']
72## END
73## N-I dash/ash stdout-json: ""
74## N-I dash/ash status: 2
75
76#### printf -v syntax error
77printf -v 'a[' %s 'foo'
78echo status=$?
79## STDOUT:
80status=2
81## END
82## N-I ash/mksh/zsh stdout: -vstatus=0
83
84#### dynamic declare instead of %s
85var=foo
86declare $var='hello there'
87argv.py "$foo"
88## STDOUT:
89['hello there']
90## END
91## N-I dash/mksh/ash STDOUT:
92['']
93## END
94
95#### dynamic declare instead of %q
96var=foo
97val='"quoted" with spaces and \'
98# I think this is bash 4.4 only.
99declare $var="${val@Q}"
100echo "$foo"
101## STDOUT:
102'"quoted" with spaces and \'
103## END
104## OK osh STDOUT:
105$'"quoted" with spaces and \\'
106## END
107## N-I dash/ash stdout-json: ""
108## N-I dash/ash status: 2
109## N-I mksh stdout-json: "\n"
110## N-I zsh stdout-json: ""
111## N-I zsh status: 1
112
113#### printf -v dynamic scope
114case $SH in mksh|zsh|dash|ash) echo not implemented; exit ;; esac
115# OK so printf is like assigning to a var.
116# printf -v foo %q "$bar" is like
117# foo=${bar@Q}
118dollar='dollar'
119f() {
120 local mylocal=foo
121 printf -v dollar %q '$' # assign foo to a quoted dollar
122 printf -v mylocal %q 'mylocal'
123 echo dollar=$dollar
124 echo mylocal=$mylocal
125}
126echo dollar=$dollar
127echo --
128f
129echo --
130echo dollar=$dollar
131echo mylocal=$mylocal
132## STDOUT:
133dollar=dollar
134--
135dollar=\$
136mylocal=mylocal
137--
138dollar=\$
139mylocal=
140## END
141## OK osh STDOUT:
142dollar=dollar
143--
144dollar='$'
145mylocal=mylocal
146--
147dollar='$'
148mylocal=
149## END
150## N-I dash/ash/mksh/zsh STDOUT:
151not implemented
152## END
153
154#### printf with too few arguments
155printf -- '-%s-%s-%s-\n' 'a b' 'x y'
156## STDOUT:
157-a b-x y--
158## END
159
160#### printf with too many arguments
161printf -- '-%s-%s-\n' a b c d e
162## STDOUT:
163-a-b-
164-c-d-
165-e--
166## END
167
168#### printf width strings
169printf '[%5s]\n' abc
170printf '[%-5s]\n' abc
171## STDOUT:
172[ abc]
173[abc ]
174## END
175
176#### printf integer
177printf '%d\n' 42
178printf '%i\n' 42 # synonym
179printf '%d\n' \'a # if first character is a quote, use character code
180printf '%d\n' \"a # double quotes work too
181printf '[%5d]\n' 42
182printf '[%-5d]\n' 42
183printf '[%05d]\n' 42
184#printf '[%-05d]\n' 42 # the leading 0 is meaningless
185#[42 ]
186## STDOUT:
18742
18842
18997
19097
191[ 42]
192[42 ]
193[00042]
194## END
195
196#### printf %6.4d -- "precision" does padding for integers
197printf '[%6.4d]\n' 42
198printf '[%.4d]\n' 42
199printf '[%6.d]\n' 42
200echo --
201printf '[%6.4d]\n' -42
202printf '[%.4d]\n' -42
203printf '[%6.d]\n' -42
204## STDOUT:
205[ 0042]
206[0042]
207[ 42]
208--
209[ -0042]
210[-0042]
211[ -42]
212## END
213
214#### printf %6.4x X o
215printf '[%6.4x]\n' 42
216printf '[%.4x]\n' 42
217printf '[%6.x]\n' 42
218echo --
219printf '[%6.4X]\n' 42
220printf '[%.4X]\n' 42
221printf '[%6.X]\n' 42
222echo --
223printf '[%6.4o]\n' 42
224printf '[%.4o]\n' 42
225printf '[%6.o]\n' 42
226## STDOUT:
227[ 002a]
228[002a]
229[ 2a]
230--
231[ 002A]
232[002A]
233[ 2A]
234--
235[ 0052]
236[0052]
237[ 52]
238## END
239
240#### %06d zero padding vs. %6.6d
241printf '[%06d]\n' 42
242printf '[%06d]\n' -42 # 6 TOTAL
243echo --
244printf '[%6.6d]\n' 42
245printf '[%6.6d]\n' -42 # 6 + 1 for the - sign!!!
246## STDOUT:
247[000042]
248[-00042]
249--
250[000042]
251[-000042]
252## END
253
254#### %06x %06X %06o
255printf '[%06x]\n' 42
256printf '[%06X]\n' 42
257printf '[%06o]\n' 42
258## STDOUT:
259[00002a]
260[00002A]
261[000052]
262## END
263
264#### %06s is no-op
265printf '(%6s)\n' 42
266printf '(%6s)\n' -42
267printf '(%06s)\n' 42
268printf '(%06s)\n' -42
269echo status=$?
270## STDOUT:
271( 42)
272( -42)
273( 42)
274( -42)
275status=0
276## END
277# mksh is stricter
278## OK mksh STDOUT:
279( 42)
280( -42)
281((status=1
282## END
283
284#### printf %6.4s does both truncation and padding
285printf '[%6s]\n' foo
286printf '[%6.4s]\n' foo
287printf '[%-6.4s]\n' foo
288printf '[%6s]\n' spam-eggs
289printf '[%6.4s]\n' spam-eggs
290printf '[%-6.4s]\n' spam-eggs
291## STDOUT:
292[ foo]
293[ foo]
294[foo ]
295[spam-eggs]
296[ spam]
297[spam ]
298## END
299
300#### printf %6.0s and %0.0s
301printf '[%6.0s]\n' foo
302printf '[%0.0s]\n' foo
303## STDOUT:
304[ ]
305[]
306## END
307## N-I mksh stdout-json: "[ ]\n["
308## N-I mksh status: 1
309
310#### printf %6.s and %0.s
311printf '[%6.s]\n' foo
312printf '[%0.s]\n' foo
313## STDOUT:
314[ ]
315[]
316## END
317## BUG zsh STDOUT:
318[ foo]
319[foo]
320## END
321## N-I mksh stdout-json: "[ ]\n["
322## N-I mksh status: 1
323
324#### printf %*.*s (width/precision from args)
325printf '[%*s]\n' 9 hello
326printf '[%.*s]\n' 3 hello
327printf '[%*.3s]\n' 9 hello
328printf '[%9.*s]\n' 3 hello
329printf '[%*.*s]\n' 9 3 hello
330## STDOUT:
331[ hello]
332[hel]
333[ hel]
334[ hel]
335[ hel]
336## END
337
338#### unsigned / octal / hex
339printf '[%u]\n' 42
340printf '[%o]\n' 42
341printf '[%x]\n' 42
342printf '[%X]\n' 42
343printf '[%X]\n' \'a # if first character is a quote, use character code
344printf '[%X]\n' \'ab # extra chars ignored
345## STDOUT:
346[42]
347[52]
348[2a]
349[2A]
350[61]
351[61]
352## END
353
354#### empty string (osh is more strict)
355printf '%d\n' ''
356## OK osh stdout-json: ""
357## OK osh status: 1
358## OK ash status: 1
359## STDOUT:
3600
361## END
362
363#### No char after ' (osh is more strict)
364
365# most shells use 0 here
366printf '%d\n' \'
367printf '%d\n' \"
368
369## OK mksh status: 1
370## STDOUT:
3710
3720
373## END
374
375#### Unicode char with ' (osh is more strict)
376
377# the mu character is U+03BC
378
379printf '%x\n' \'μ
380
381## STDOUT:
3823bc
383## END
384## BUG dash/mksh/ash STDOUT:
385ce
386## END
387
388#### negative numbers with unsigned / octal / hex
389printf '[%u]\n' -42
390printf '[%o]\n' -42
391printf '[%x]\n' -42
392printf '[%X]\n' -42
393## STDOUT:
394[18446744073709551574]
395[1777777777777777777726]
396[ffffffffffffffd6]
397[FFFFFFFFFFFFFFD6]
398## END
399
400# osh DISALLOWS this because the output depends on the machine architecture.
401## N-I osh stdout-json: ""
402## N-I osh status: 1
403
404#### printf floating point (not required, but they all implement it)
405printf '[%f]\n' 3.14159
406printf '[%.2f]\n' 3.14159
407printf '[%8.2f]\n' 3.14159
408printf '[%-8.2f]\n' 3.14159
409printf '[%-f]\n' 3.14159
410printf '[%-f]\n' 3.14
411## STDOUT:
412[3.141590]
413[3.14]
414[ 3.14]
415[3.14 ]
416[3.141590]
417[3.140000]
418## END
419## N-I osh stdout-json: ""
420## N-I osh status: 2
421
422#### printf floating point with - and 0
423printf '[%8.4f]\n' 3.14
424printf '[%08.4f]\n' 3.14
425printf '[%8.04f]\n' 3.14 # meaning less 0
426printf '[%08.04f]\n' 3.14
427echo ---
428# these all boil down to the same thing. The -, 8, and 4 are respected, but
429# none of the 0 are.
430printf '[%-8.4f]\n' 3.14
431printf '[%-08.4f]\n' 3.14
432printf '[%-8.04f]\n' 3.14
433printf '[%-08.04f]\n' 3.14
434## STDOUT:
435[ 3.1400]
436[003.1400]
437[ 3.1400]
438[003.1400]
439---
440[3.1400 ]
441[3.1400 ]
442[3.1400 ]
443[3.1400 ]
444## END
445## N-I osh STDOUT:
446---
447## END
448## N-I osh status: 2
449
450#### printf eE fF gG
451printf '[%e]\n' 3.14
452printf '[%E]\n' 3.14
453printf '[%f]\n' 3.14
454# bash is the only one that implements %F? Is it a synonym?
455#printf '[%F]\n' 3.14
456printf '[%g]\n' 3.14
457printf '[%G]\n' 3.14
458## STDOUT:
459[3.140000e+00]
460[3.140000E+00]
461[3.140000]
462[3.14]
463[3.14]
464## END
465## N-I osh stdout-json: ""
466## N-I osh status: 2
467
468#### printf backslash escapes
469argv.py "$(printf 'a\tb')"
470argv.py "$(printf '\xE2\x98\xA0')"
471argv.py "$(printf '\044e')"
472argv.py "$(printf '\0377')" # out of range
473## STDOUT:
474['a\tb']
475['\xe2\x98\xa0']
476['$e']
477['\x1f7']
478## END
479## N-I dash STDOUT:
480['a\tb']
481['\\xE2\\x98\\xA0']
482['$e']
483['\x1f7']
484## END
485
486#### printf octal backslash escapes
487argv.py "$(printf '\0377')"
488argv.py "$(printf '\377')"
489## STDOUT:
490['\x1f7']
491['\xff']
492## END
493
494#### printf unicode backslash escapes
495argv.py "$(printf '\u2620')"
496argv.py "$(printf '\U0000065f')"
497## STDOUT:
498['\xe2\x98\xa0']
499['\xd9\x9f']
500## END
501## N-I dash/ash STDOUT:
502['\\u2620']
503['\\U0000065f']
504## END
505
506#### printf invalid backslash escape (is ignored)
507printf '[\Z]\n'
508## STDOUT:
509[\Z]
510## END
511
512#### printf % escapes
513printf '[%%]\n'
514## STDOUT:
515[%]
516## END
517
518#### printf %b backslash escaping
519printf '[%s]\n' '\044' # escapes not evaluated
520printf '[%b]\n' '\044' # YES, escapes evaluated
521echo status=$?
522## STDOUT:
523[\044]
524[$]
525status=0
526## END
527
528#### printf %b with \c early return
529printf '[%b]\n' 'ab\ncd\cxy'
530echo $?
531## STDOUT:
532[ab
533cd0
534## END
535
536#### printf %c -- doesn't respect UTF-8! Bad.
537twomu=$'\u03bc\u03bc'
538printf '[%s]\n' "$twomu"
539printf '%c' "$twomu" | wc --bytes
540## STDOUT:
541[μμ]
5421
543## END
544## N-I dash STDOUT:
545[$\u03bc\u03bc]
5461
547## END
548## N-I ash STDOUT:
549[\u03bc\u03bc]
5501
551## END
552## N-I osh STDOUT:
553[μμ]
5540
555## END
556
557#### printf invalid format
558printf '%z' 42
559echo status=$?
560printf '%-z' 42
561echo status=$?
562## STDOUT:
563status=1
564status=1
565## END
566# osh emits parse errors
567## OK dash/osh STDOUT:
568status=2
569status=2
570## END
571
572#### printf %q
573x='a b'
574printf '[%q]\n' "$x"
575## STDOUT:
576['a b']
577## END
578## OK bash/zsh STDOUT:
579[a\ b]
580## END
581## N-I ash/dash stdout-json: "["
582## N-I ash status: 1
583## N-I dash status: 2
584
585#### printf %6q (width)
586# NOTE: coreutils /usr/bin/printf does NOT implement this %6q !!!
587x='a b'
588printf '[%6q]\n' "$x"
589printf '[%1q]\n' "$x"
590## STDOUT:
591[ 'a b']
592['a b']
593## END
594## OK bash/zsh STDOUT:
595[ a\ b]
596[a\ b]
597## END
598## N-I mksh/ash/dash stdout-json: "[["
599## N-I mksh/ash status: 1
600## N-I dash status: 2
601
602#### printf negative numbers
603printf '[%d] ' -42
604echo status=$?
605printf '[%i] ' -42
606echo status=$?
607
608# extra LEADING space too
609printf '[%d] ' ' -42'
610echo status=$?
611printf '[%i] ' ' -42'
612echo status=$?
613
614# extra TRAILING space too
615printf '[%d] ' ' -42 '
616echo status=$?
617printf '[%i] ' ' -42 '
618echo status=$?
619
620# extra TRAILING chars
621printf '[%d] ' ' -42z'
622echo status=$?
623printf '[%i] ' ' -42z'
624echo status=$?
625
626exit 0 # ok
627
628## STDOUT:
629[-42] status=0
630[-42] status=0
631[-42] status=0
632[-42] status=0
633[-42] status=1
634[-42] status=1
635[-42] status=1
636[-42] status=1
637## END
638# zsh is LESS STRICT
639## OK zsh STDOUT:
640[-42] status=0
641[-42] status=0
642[-42] status=0
643[-42] status=0
644[-42] status=0
645[-42] status=0
646[0] status=1
647[0] status=1
648## END
649
650# osh is like zsh but has a hard failure (TODO: could be an option?)
651## OK osh STDOUT:
652[-42] status=0
653[-42] status=0
654[-42] status=0
655[-42] status=0
656[-42] status=0
657[-42] status=0
658status=1
659status=1
660## END
661
662# ash is MORE STRICT
663## OK ash STDOUT:
664[-42] status=0
665[-42] status=0
666[-42] status=0
667[-42] status=0
668[0] status=1
669[0] status=1
670[0] status=1
671[0] status=1
672## END
673
674
675#### printf + and space flags
676# I didn't know these existed -- I only knew about - and 0 !
677printf '[%+d]\n' 42
678printf '[%+d]\n' -42
679printf '[% d]\n' 42
680printf '[% d]\n' -42
681## STDOUT:
682[+42]
683[-42]
684[ 42]
685[-42]
686## END
687## N-I osh stdout-json: ""
688## N-I osh status: 2
689
690#### printf # flag
691# I didn't know these existed -- I only knew about - and 0 !
692# Note: '#' flag for integers outputs a prefix ONLY WHEN the value is non-zero
693printf '[%#o][%#o]\n' 0 42
694printf '[%#x][%#x]\n' 0 42
695printf '[%#X][%#X]\n' 0 42
696echo ---
697# Note: '#' flag for %f, %g always outputs the decimal point.
698printf '[%.0f][%#.0f]\n' 3 3
699# Note: In addition, '#' flag for %g does not omit zeroes in fraction
700printf '[%g][%#g]\n' 3 3
701## STDOUT:
702[0][052]
703[0][0x2a]
704[0][0X2A]
705---
706[3][3.]
707[3][3.00000]
708## END
709## N-I osh STDOUT:
710---
711## END
712## N-I osh status: 2
713
714#### Runtime error for invalid integer
715x=3abc
716printf '%d\n' $x
717echo status=$?
718printf '%d\n' xyz
719echo status=$?
720## STDOUT:
7213
722status=1
7230
724status=1
725## END
726# zsh should exit 1 in both cases
727## BUG zsh STDOUT:
7280
729status=1
7300
731status=0
732## END
733# fails but also prints 0 instead of 3abc
734## BUG ash STDOUT:
7350
736status=1
7370
738status=1
739## END
740# osh doesn't print anything invalid
741## OK osh STDOUT:
742status=1
743status=1
744## END
745
746#### %(strftime format)T
747# The result depends on timezone
748export TZ=Asia/Tokyo
749printf '%(%Y-%m-%d)T\n' 1557978599
750export TZ=US/Eastern
751printf '%(%Y-%m-%d)T\n' 1557978599
752echo status=$?
753## STDOUT:
7542019-05-16
7552019-05-15
756status=0
757## END
758## N-I mksh/zsh/ash STDOUT:
759status=1
760## END
761## N-I dash STDOUT:
762status=2
763## END
764
765#### %(strftime format)T doesn't respect TZ if not exported
766
767# note: this test leaks! It assumes that /etc/localtime is NOT Portugal.
768
769TZ=Portugal # NOT exported
770localtime=$(printf '%(%Y-%m-%d %H:%M:%S)T\n' 1557978599)
771
772# TZ is respected
773export TZ=Portugal
774tz=$(printf '%(%Y-%m-%d %H:%M:%S)T\n' 1557978599)
775
776#echo $localtime
777#echo $tz
778
779if ! test "$localtime" = "$tz"; then
780 echo 'not equal'
781fi
782## STDOUT:
783not equal
784## END
785## N-I mksh/zsh/ash/dash stdout-json: ""
786
787#### %(strftime format)T TZ in environ but not in shell's memory
788
789# note: this test leaks! It assumes that /etc/localtime is NOT Portugal.
790
791# TZ is respected
792export TZ=Portugal
793tz=$(printf '%(%Y-%m-%d %H:%M:%S)T\n' 1557978599)
794
795unset TZ # unset in the shell, but still in the environment
796
797localtime=$(printf '%(%Y-%m-%d %H:%M:%S)T\n' 1557978599)
798
799if ! test "$localtime" = "$tz"; then
800 echo 'not equal'
801fi
802
803## STDOUT:
804not equal
805## END
806## N-I mksh/zsh/ash/dash stdout-json: ""
807
808#### %10.5(strftime format)T
809# The result depends on timezone
810export TZ=Asia/Tokyo
811printf '[%10.5(%Y-%m-%d)T]\n' 1557978599
812export TZ=US/Eastern
813printf '[%10.5(%Y-%m-%d)T]\n' 1557978599
814echo status=$?
815## STDOUT:
816[ 2019-]
817[ 2019-]
818status=0
819## END
820## N-I dash/mksh/zsh/ash STDOUT:
821[[status=1
822## END
823## N-I dash STDOUT:
824[[status=2
825## END
826
827#### Regression for 'printf x y'
828printf x y
829printf '%s\n' z
830## STDOUT:
831xz
832## END
833
834#### bash truncates long strftime string at 128
835
836case $SH in (ash|dash|mksh|zsh) exit ;; esac
837
838strftime-format() {
839 local n=$1
840
841 # Prints increasingly long format strings:
842 # %(%Y)T %(%Y)T %(%Y%Y)T ...
843
844 echo -n '%('
845 for i in $(seq $n); do
846 echo -n '%Y'
847 done
848 echo -n ')T'
849}
850
851printf $(strftime-format 1) | wc --bytes
852printf $(strftime-format 10) | wc --bytes
853printf $(strftime-format 30) | wc --bytes
854printf $(strftime-format 31) | wc --bytes
855printf $(strftime-format 32) | wc --bytes
856
857case $SH in
858 (*/_bin/cxx-dbg/*)
859 # Ensure that oils-for-unix detects the truncation of a fixed buffer.
860 # bash has a buffer of 128.
861
862 set +o errexit
863 (
864 printf $(strftime-format 1000)
865 )
866 status=$?
867 if test $status -ne 1; then
868 echo FAIL
869 fi
870 ;;
871esac
872
873## STDOUT:
8744
87540
876120
877124
8780
879## END
880## OK osh STDOUT:
8814
88240
883120
884124
885128
886## END
887
888## N-I ash/dash/mksh/zsh STDOUT:
889## END
890
891
892#### printf with explicit NUL byte
893case $SH in (dash|ash) return ;; esac
894
895printf $'x\U0z'
896
897printf $'\U0z'
898
899## stdout-json: "x"
900## OK zsh stdout-repr: "x\0z\0z"
901## N-I dash/ash stdout-json: ""