本文记录的是shell之printf格式化输出.

首先看看其 man page 内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
NAME
printf -- formatted output

SYNOPSIS
printf format [arguments ...]

DESCRIPTION
The printf utility formats and prints its arguments, after
the first, under control of the format. The format is a character string which contains three types of objects: plain characters,
which are simply copied to standard output, character escape
sequences which are converted and copied to the standard
output, and format specifications, each of which causes printing
of the next successive argument.

The arguments after the first are treated as strings if the
corresponding format is either c, b or s;otherwise it is evaluated
as a C constant, with the following extensions:

o A leading plus or minus sign is allowed.
o If the leading character is a single or double quote, the value is the ASCII code of the next character.

从上述 man page 内容可以看出:

  1. printf通过format字符串格式化输出其后的arguments
  2. format字符串包含三种类型对象: 普通字符串,该对象输出时会直接拷贝到标准输出STDOUT;字符转义序列,通过字符转义之后输出到标准输出;格式说明符,每一个格式说明符对应输出相应的argument
  3. 如果对应的格式指示符是%c/%b/%s时,相对应的参数都视为字符串,否则它们会被解释为C语言的数字常量: 在其开头可使用正负号标识;如果字符串开头是单引号'或者双引号",那么打印输出的值是紧跟着单引号或者双引号后的那个字符的ASCII

关于%c/%b/%s,其 man page的说明如下:

1
2
3
4
5
6
7
8
9
c: The first character of argument is printed.

s: Characters from the string argument are printed until
the end is reached or until the number of characters indicated by
the precision specification is reached; however if the precision
is 0 or missing, all characters in the string are printed.

b: As for s, but interpret character escapes in backslash
notation in the string argument.

从上面的文档可以看出:

  1. 若使用%c指示符,则指打印输出参数中的第一个字符,会忽略其它字符;
  2. 若使用%s指示符,则打印整个字符串参数,或者打印控制精度控制的字符个数,如果精度值是0或者没有控制精度值,则打印所有的字符值。
  3. 使用%s时,在格式指示符中的转义字符能够被解析,而参数列表中的转义字符则不会被解析,但是若使用%b指示符,printf会解析格式指示符和参数列表中的转义字符,其后的参数列表需要用双引号

对于以上说明,举以下几个例子:

1
2
3
$ printf "hello world\n"
hello world
$

上例中输出了普通字符串+转义字符,可以看出,printf不同于echo会默认输出\n,使用printf输出新的一行必须在末尾加上\n

1
2
3
$ printf "hello %c, %s\n" "China" "World"
hello C, World
$

上例中使用了%c格式指示符,只打印出来对应参数的第一个字符,忽略了该参数的其他字符

1
2
3
4
5
$ printf "hello\tworld, welcome to%s\n" "\tChina"
hello world, welcome to\tChina
$ printf "hello\tworld, welcome to%b\n" "\tChina"
hello world, welcome to China
$

上例中第一种情况,只解析了format字符串中的转义字符,而未解析使用%s格式指示符控制的参数中的转义字符,而使用%b控制时,能够解析参数中的转义字符.

1
2
3
4
5
$ printf "hello %b see u\n" hjkl\taa
hello hjkltaa see u
$ printf "hello %b see u\n" "hjkl\taa"
hello hjkl aa see u
$

上例是使用%b指示符解析参数中的转义字符时,参数未用双引号包含和使用双引号包含的情况对比。

1
2
3
4
5
$ printf "hello u are %s\n" "'a"
hello u are 'a
$ printf "hello u are %d\n" "'a"
hello u are 97
$

上例是 参数字符串开头是单引号'或者双引号"时,那么打印输出的值是紧跟着单引号或者双引号后的那个字符的ASCII值 的实例

关于格式方面的几个转义字符:

1
2
3
4
5
6
7
8
9
10
\a      警告声音输出.
\b 退格键<backspace>.
\c 忽略该字符串中后面的任何字符(包括普通字符、转义字符以及参数)以及格式字符串中的字符,该规则只有在`%b`格式指示符控制下参数字符串中有效
\f 换页<form-feed>.
\n 输出新的一行.
\r Enter键,<carriage return> character.
\t 水平制表符[tab].
\v 垂直制表符.
\' 单引号.
\\ 反斜线.

下面说说上述转义字符中的\c\f

1
2
$ printf "ab%sc%bdef%s" "AAA\n" "JQK\cioio" "China"
abAAA\ncJQK$

\c需要与%b配合使用,若使用的是%s,则会原样输出,如上例的AAA\n参数;注意上例中的%b和参数JQK\cioio,遇到\c后,忽略了该参数后的字符ioio,也忽略了%b指示符后所有格式说明,包括普通字符def以及格式占位符%s

1
2
3
4
5
$ printf "hello a\fbbbbbkkdkkdk\flodldidkcmmjjd\n"
hello a
bbbbbkkdkkdk
lodldidkcmmjjd
$

上述是\f转义字符的示例,我也不知道该做何解释,领悟吧.

每一个格式占位符都是以%引出,接下来的格式说明符可以按以下顺序包括:

  1. 0个或者多个标志位,这里暂且称为位置标识,稍后解释。
  2. Field Width, 字段宽度。
  3. 精度控制
  4. 参数格式类型

其中,上述位置标识包括:

  • #: 该字符指示参数应该进行格式转换。对于%c/%d/%s格式类型,该选项不产生影响;对于%o强制其精度值增加,使其输出字符串的第一个字符为0;对于%x/%X,会有0x(0X)加在参数开头; 对于%e/%E/%f/%g/%G,在输出结果中总会包含有小数点,即使小数点后没有任何数字.
  • -: 该字符指定对应的参数字段使用左对齐方式.
  • +: 该字符指定,在输出有符号形式的数字时,在数字前总应该有相应符号.
  • : 空格符,该符号指示在输出有符号形式的数字时,如果该数字是正数,则在该正数前应该有一空格,如果是负数,则在空格位置是-;如果使用了+位置标识,则该位置标识不起作用.
  • 0: 该指示符表示,应该使用0填充代替空格填充,如果使用-位置标识,则该位置标识不起作用.

位置标识的示例如下:

1
2
3
4
5
$ printf "hello %#s\n" "world, hey"
hello world, hey
$ printf "hello %s\n" "world, hey"
hello world, hey
$

上例中看出#%s格式类型无影响

1
2
3
4
5
6
7
8
9
$ printf "this is octonary number: %o\n" "123"
this is octonary number: 173
$ printf "this is octonary number: %#o\n" "123"
this is octonary number: 0173
$ printf "this is octonary number: %x\n" "123"
this is octonary number: 7b
$ printf "this is octonary number: %#x\n" "123"
this is octonary number: 0x7b
$

上例中看出,对于八进制和十六进制数字,使用#后会在参数前添加0或者0x

1
2
3
$ printf "Field width: |%10s|\t|%-10s|\n" "hello" "world"
Field width: | hello| |world |
$

上例中看出,使用-指示符之后,参数实行左对齐

1
2
3
4
5
$ printf "signed number: %+d\n" 3456
signed number: +3456
$ printf "signed number: %+d\n" -3456
signed number: -3456
$

上例中看出,使用+指示符之后,输出有符号数字时会在数字前加上+/-

1
2
3
4
5
6
7
8
9
$ printf "padding number:% d\n" 3456
padding number: 3456
$ printf "padding number:% d\n" -3456
padding number:-3456
$ printf "padding number:%+ d\n" -3456
padding number:-3456
$ printf "padding number:%+ d\n" 3456
padding number:+3456
$

上例中看出使用空格指示符之后,输出正数时,会在正数前添加一个空格,输出负数时,会使用-填补空格处,如果同时使用了+指示符,则空格指示符不起作用

1
2
3
4
5
6
7
$ printf "padding number:|%5d|\n" 23
padding number:| 23|
$ printf "padding number:|%05d|\n" 23
padding number:|00023|
$ printf "padding number:|%-05d|\n" 23
padding number:|23 |
$

上例中看出使用0指示符之后,输出的数字会在左边补0,如果同时使用了左对齐-指示符,那么0指示符不起作用

接下来说说字段宽度控制。

1
2
3
4
5
6
$ printf "padding number:|%5d|\n" -2345678
padding number:|-2345678|
$ printf "padding number:|%5d|\n" -23
padding number:| -23|
$ printf "padding number:|%-5d|\n" -23
padding number:|-23 |

如上例所示,使输出的整数5个字符宽度,如果数字宽度大于5,那么使用实际宽度(不进行截断), 如果数字宽度小于5,那个左边使用空格占位补齐,如果使用的左对齐方式,那么右边使用空格占位补齐

接下来说说输出的精度控制。精度控制使用一个小数点加上紧接着的数字(精度值)表示.对于%e%f格式类型,精度控制指定的是小数点后可保留的小数点位数;对于字符串参数则表示可输出的最大字符个数;如果小数点后的精度值没有,那么则是将精度值视为0

1
2
3
4
5
6
7
8
9
$ printf "padding number:|%.2f|\n" -23.9876543
padding number:|-23.99|
$ printf "padding number:|%.f|\n" -23.9876543
padding number:|-24|
$ printf "padding number:|%.2s|\n" "hello"
padding number:|he|
$ printf "padding number:|%.s|\n" "hello"
padding number:||
$

上例中第一种情形,保留2位小数,第二种情形,没有精度控制值,只有.,表示不保留小数部分,第三和第四种情形是针对字符的输出控制。

最后说明,参数格式类型使用单一字符标识(one of diouxXfFeEgGaAcsb).从之前的例子中已经看到部分类型的使用。

对于字段宽度控制和精度控制中,可能会使用*代替数字,这种情况下,由参数控制字段的宽度和输出精度。

对于参数格式类型控制字符diouxXfFeEgGaAcsb说明如下:

diouXx: 指示参数会以带有符号的十进制数输出(d或者i), 无符号八进制(o), 无符号十进制(u),以及无符号十六进制(xX)

fF: 指示参数会以[-]ddd.ddd的格式输出,其中小数点后的d个数等同于参数的精度控制,如果没有精度控制值,则使用默认值6;如果精度值是0,那么不会输出小数点及任何小数部分. The values infinity and NaN are printed as inf and nan, respectively.

eE: 指示参数以[-d.ddd+-dd]的格式输出,在小数点前面会有一位数字,小数点后的数字位数由精度值控制,如果没有精度控制值,则使用默认值6;The values infinity and NaN are printed as inf and nan, respectively.

gG: The argument is printed in style f (F) or in style e (E) whichever gives full precision in minimum space.

aA: The argument is printed in style [-h.hhh+-pd] where there is one digit before the hexadecimal point and the number after is equal to the precision specification for the
argument; when the precision is missing, enough digits are
produced to convey the argument's exact double-precision floating-point representation. The values infinity and NaN are printed as inf and nan, respectively.

%: Print a %; no argument is used.

In no case does a non-existent or small field width cause truncation of a field; padding takes place only if the specified
field width exceeds the actual width.

下面看几个例子来理解。

1
2
3
4
5
6
7
$ printf "float number: %f\n" 1342.78654329
float number: 1342.786543
$ printf "float number: |%.4f|\n" -1342.78654329
float number: |-1342.7865|
$ printf "float number: |%.f|\n" -1342.78654329
float number: |-1343|
$

上面的例子中第一种情形展示的精度控制默认值(小数点后6位);第二种情形表示保留小数点后4位;第三种情形表示没有精度控制值时忽略小数点及小数。

1
2
3
4
5
6
7
$ printf "float number: |%.e|\n" -1342.78654329
float number: |-1e+03|
$ printf "float number: |%e|\n" -1342.78654329
float number: |-1.342787e+03|
$ printf "float number: |%.3e|\n" -1342.78654329
float number: |-1.343e+03|
$

上面是eE的例子。

1
2
3
4
$ printf "significant digit control: %.4G\n" 123456.3456256789
significant digit control: 1.235E+05
$ printf "significant digit control: %.4G\n" 56.3456256789
significant digit control: 56.35

最后再次说明一种格式:

%N.nf: 其中N表示浮点数所占有的总的宽度,包括小数点,n表示小数点的个数,如下:

1
2
3
$ printf "|%6.2f|\n"  567.98765442
|567.99|
$