如何添加前导字符以将字符串填充到一定长度?
假设我想在字符串前面添加零,如果该字符串短于 20 个字符:
printf '%020s\n' "$line"
但是,这会用前导空格填充字符串,而不是其他任何内容,例如零。
该字符串可以是包含数字和其他字符的随机字符串。
我不想将字符串拆分为 number + rest for printf '%020i' $number
or printf '%020d $number'
。
我当前的输出是:
shortstring
我想要的输出是:
000000000shortstring
答案1
只需手动进行填充即可。如果您坚持使用 POSIX 工具,我认为没有更直接的解决方案。
while [ ${#line} -lt 20 ]; do
line=0$line
done
或者
n=${#line}
while [ $n -lt 20 ]; do
printf '0'
n=$((n-1))
done
printf "$line\n"
当然,在 zsh 中,有相应的语法。与上面的代码片段不同,如果字符串长度超过 20 个字符,此代码片段将截断该字符串。如果您不想截断,可以附加字符串的尾部。
print -r ${(l:20::0:)line}
print -r ${(l:20::0:)line}${line:20}
答案2
正如 @Gnouc 已经指出的那样,你已经成功了一半:
line='some chars'
printf '%0'$((20-${#line}))'d%s\n' 0 "$line"
###OUTPUT###
0000000000some chars
当然,即使行长超过 20 个字符,你仍然会得到 0,而这只是如果 printf
不会因错误而失败,因为它无法正确处理-
格式字符串中的破折号(这显然是更有可能的情况)。不过,这很容易处理:
printf "%.$((((l=20-${#line})>0)*l))d%s\n" 0 "$line"
该格式字符串使用printf
's%.
精确%.0d
标志将其小数参数零填充到指示的长度,或者 - 在出现空或空精度的情况下%.d
- 以其他方式将任何前导 0 截断为指示的数字。像这样:
printf '#%.0d%c%.5d' 0000000 ':separator' 4
#:00004
(注意:该%c
格式打印其关联参数的第一个字符)
算术$((
表达式))
首先将 var 设置$l
为 20 减号,然后如果小于或大于 0,则分别${#line}
计算为 0 或 1 ,最后将其乘以。因此,当 20-小于零时,您会执行此操作,但当它大于零时,您会执行此操作。$l
$l
${#line}
$((0*l))
$((1*l))
这样你总是仅打印与 20 和 之间的差值相同数量的前导零${#line}
。像这样:
line='some chars'
printf "#%.$((((l=20-${#line})>0)*l))d%s\n" 0 "$line"
#0000000000some chars
和...
line='some chars'
printf "#%.$((((l=4-${#line})>0)*l))d%s\n" 0 "$line"
#some chars
你可以使用相同的%.
精确类型字段的形式s
也是如此,例如:
line='some chars'
printf "#%.$((((l=4-${#line})>0)*l))d%.4s\n" 0 "$line"
#some
...根据需要截断。
这里是一个循环 - 以下迭代 5 次:
line=
until line=fivec$line; [ $((l)) -lt 0 ]
do printf "#%.$((((l=20-${#line})>0)*l))d%s\n" 0 "$line"
done
#000000000000000fivec
#0000000000fivecfivec
#00000fivecfivecfivec
#fivecfivecfivecfivec
#fivecfivecfivecfivecfivec
值得注意的是,显示的零根本不包含在任何 shell 变量的值中(更不用说),并且仅按照其格式字符串指示$line
自动生成。printf
这是具有更高精度的相同循环%.20s
:
line=
until line=fivec$line; [ $((l)) -lt 0 ]
do printf "#%.$((((l=20-${#line})>0)*l))d%.20s\n" 0 "$line"
done
#000000000000000fivec
#0000000000fivecfivec
#00000fivecfivecfivec
#fivecfivecfivecfivec
#fivecfivecfivecfivec
您可以用它做的另一件事非常有趣。当您printf
将 动态生成 0 的功能与您结合起来时,$IFS
您可以根据需要多次使用任何字符串,例如:
IFS=0 ; printf '10 lines\n%s' $(printf %010d) ; unset IFS
###OUTPUT###
10 lines
10 lines
10 lines
10 lines
10 lines
10 lines
10 lines
10 lines
10 lines
10 lines
答案3
%s
是字符串格式规范。零填充仅定义为数字转换而存在:
0对于 d、i、o、u、x、X、a、A、e、E、f、F、g 和 G 转换说明符,前导零(跟随任何符号或基数指示)用于填充到字段宽度 ...对于其他转换,行为未定义。
如果您$line
是数字,请使用:
printf '%020i\n' $line
以获得您想要的结果(或其他数字格式说明符之一)。如果您的值更复杂但以数字开头,您可以使用:
printf '%020i %s\n' $num $restofline
一次将数字和字符串组合在一起。
答案4
如果你可以使用perl
:
$ perl -e 'print sprintf "%020s\n","shortstring"'
000000000shortstring
对于更一般的情况:
$ perl -e 'print sprintf "%020s\n",shift' shortstring
000000000shortstring
$ perl -e 'print sprintf "%020s\n", $_ for @ARGV' shortstring qwerty
000000000shortstring
00000000000000qwerty
笔记
某些平台可以使用前导零格式化输出字符串,至少在AIX
.使用GNU gcc
,编译时会出现警告:
$ cat test.c
#include <stdio.h>
int main() {
printf("%08s\n", "qwerty");
return 0;
}
然后:
$ gcc -Wall test.c
test.c: In function ‘main’:
test.c:4:5: warning: '0' flag used with ‘%s’ gnu_printf format [-Wformat]
如果您不想使用perl
,您可以尝试:
$ line=shortstring
$ printf "%0$((20-${#line}))d%s" 0 $line
000000000shortstring
当字符串长度大于 20 时,此方法无法处理。您可以使用 @mikeserv answer 来处理该问题或使用 before 进行检查printf
。