Format String

113 阅读4分钟

关于

linux命令或者接口中会存在很多format string的场景,本文将整理使用到的各种场景和解释。

printf

Format String: %[flags][minimum_field_width][.precision][length_modifier]conversion_specifier
不想写解释了, 直接看man 3 printf, 里面描述的很清晰

# # 标识添加各进制的前缀, 比如十六进制的0x等
# 0 padding zero
# - left justiment, 默认右对齐
# + 显式显示正负号
# ' 显示thousands separator, BASH的printf支持, GCC版本貌似不支持
flags: [+-0#']

# Minimum Field Width 
标识整个字段的最小宽度, 如果超过, 不会截断

# Precision 
对于d, i, o, u, x, X的conversion specifier, 表示最小出现的数字个数; 而对于a, A, f, F, e, E, 表示小数点后数字个数; 对于g, G, 表示最大有效位数; 对于s, S, 表示最大现实字符个数

# minimum_field_width和precision可以使用后面的argument参数作为值, 使用 '*' 标识, 比如 
int width = 3;
int num = 2;
printf("%*d\n", width, num);
printf("%2$%*1$d\n");
# 打印出 __2
# 以上两种方式一样, 第一种使用*占位一个参数作为minimum_field_width, 第二种更加隐晦, 她使用 '$m$' 格式引用后面的参数, 然后使用这种形式引用第一个参数作为minimum_field_width。

# Length Modifier
A length modifier is used to exactly specify the type of the matching argument.
在printf中一般不使用, 标识被匹配参数的类型或者叫做长度, 比如
printf("%hhd\n", 257)
打印 1, 因为 'hh' 表示后面的参数是一个字节, 最大0xff, 超过就wrap

# Conversion Specifier
d, i, u, x, X, o, O, f, F, e, E, g, G, c, s, % etc

# 当conversion specifier为x时, 有pricision场景, flag 0省略, 所以前面5位空格
printf "%08.3x" 7  -> _____007 
printf "%-08.3x" 7 -> 007_____

flags: 0 表示不足长度8使用0填充
width: 8 表示最长长度为8
precision: 3 使用x时, precision表示最短长度
specifier: x 表示使用16进制表示

sscanf

sscanf的format格式介绍

%[*][maximum_field_width][length_modifier]conversion_specifier

format stringDescriptionExampleMatching InputResults
*匹配输入但不赋值int anInput;
scanf("%*s %i", &anInput);
Age: _29anInput = 29, return value = 1
maximum_field_width标识匹配输入的字节个数int anInt;
char s[10];
scanf("%2i", &anInt);
scanf("%9s", s);
2345
VeryLongString
anInt==23,
return value==1
s=="VeryLongS"
return value==1
length_modifier标识目的匹配参数的类型(或者叫做大小)char *src="12";
char des[2];
sscanf(src, "%2hhx\n", des);
printf("des[0] = %d\n",des[0]);
des[0] = 12

sscanf返回值

正常情况成功返回成功匹配和赋值的个数。 如果部分成功匹配和赋值, 返回成功的个数; 如果input结束(EOF)时, 没有匹配成功或者匹配失败, 返回EOF, 使用errno查看。

// https://wpollock.com/CPlus/PrintfRef.htm#printfLen
char buf[BUFSIZ], junk[BUFSIZ];
int income;

fprintf( stderr, "Please enter your income: " );
// Loop until the user enters a correct value:
while ( fgets( buf, sizeof(buf), stdin ) != NULL )
{
   if ( sscanf( buf, "%i%[^\n]", &income, junk ) == 1 )
      break;
   // Do some sort of error processing:
   fprintf( stderr, "\nError reading your income, please try again.\n" );
   fprintf( stderr, "Please enter your income: " );
}
// income correctly enters read at this point.

hexstr2buf

unsigned char *hexstr2buf(const char *str)
{
	size_t len = strlen(str);
	if (len % 2 != 0)
		return NULL;
	size_t res_len = len / 2 + 1;
	unsigned char *res = (unsigned char *)malloc(res_len);

	for (int i = 0; i < len; i += 2)
	{
		sscanf(str + i, "%2hhx", res + i / 2);
	}
	*(res + res_len - 1) = '\0';
	return res;
}

hexdump

  1. KB and K(KiB)
    hexdump可以使用K或者KiB代表1024字节, KB代表1000字节
  2. Format and Color in HEXDUMP format string格式: -e 'iterator_count/byte_count "format"', 其中iterator_count, byte_count其中只要有一个存在, 那 / 是必须的。format 必须使用双引号, format%开头, 类似printf。常见的format有:
    _a 每次开始递归迭代时执行, 比如 -e '"%08.8_ax"'
    _A 所有迭代完成后执行, 一般最后输出所有长度, 比如: -e '"%08.8_Ax"'
    _p 按照当前字符集输出字符, 不能打印使用.代替
    _L 添加颜色
    x 16进制转化
# 执行完所有转换添加cyan颜色的地址标识, 然后换行;
# 每次开始递归迭代时添加cyan颜色的起始地址标识, 空格两个
# 每次迭代八次, 每次取两个字节, 如果开头两个字节是0x6f72则使用绿色显示, 否则使用红色, 其他使用默认颜色
# 每次迭代十六次, 使用默认字符集显示对应字节的字符, 并且前后使用 "|" 分割, 最后换行
hexdump -v -e '"%08_Ax_L[cyan]\n"' -e '"%08_ax_L[cyan]  " 8/2 "%04x_L[green:0x6f72@0-1,!red:0x6f72@0-1] " "  |"' -e '16/1 "%_p" "|" "\n"' -n 64 /etc/passwd
# -v 不省略重复字节, 默认会使用 * 省略重复字节
# -e 提供format, 可以有多个, 多个的递归迭代从当前开头开始
# -f 提供format文件
# -n 使用前64个字节
  1. xxd和hexdump分场景使用, hexdump支持定制, 功能更丰富, 但是简单场景xxd似乎更适合点 :)
echo -en "tls13 $label" | hexdump -v -e '/1 "%02x"'
echo -en "tls13 $label" | xxd -p