bash中的字符串处理@输出和格式化打印@echo@printf

文章目录

abstract

Bash 字符串输出和格式化打印方法

bash内置两个输出字符串的命令

echo用来将其参数输出到stdout,通常用于打印一段字符串

printf除了打印字符串,还可以用于格式化其参数(字符串),比echo更强大,对于高级脚本,或者测试一些shell特性,这是必不可少的工具.

  • echo: Write arguments to the standard output.
  • printf:Formats and prints ARGUMENTS under control of the FORMAT.

echo仅有三个选项,比较简单,详情另见它文,本文主要介绍printf

尽管printf更强大,但是echo配合bash的一些字符串处理语法(参数替换),也可以做许多处理,因此echo用的不少.

printf版本

printf 既是一个 Shell 内建命令(Builtin),也是一个独立的可执行文件(External Command)。

bash 复制代码
$ type -a printf
printf is a shell builtin
printf is /usr/sbin/printf
printf is /usr/bin/printf
printf is /sbin/printf
printf is /bin/printf

Shell 内建版 (Shell Builtin)

当你直接在终端输入 printf 时,Bash 默认会运行这个版本。

  • 速度快: 它是 Bash 进程的一部分,不需要创建新的子进程。
  • 优先级最高: 除非你指定路径(如 /usr/bin/printf),否则 Bash 总是先用自己的"亲儿子"。

外部命令版 (External Binary)

那些位于 /usr/bin//bin/ 下的文件是独立的存在。

  • 兼容性: 主要是为了兼容那些不具备 printf 功能的老旧 Shell(比如原始的 Bourne Shell)。
  • 系统调用: 如果你在编写一个非 Bash 的脚本(比如 Python 脚本)并调用系统命令,系统会去寻找这些物理文件。

虽然 99% 的情况下它们表现一致,但还是有细微差别:

功能集: Bash 内建的 printf 通常功能更多。例如,Bash 版支持使用 -v 选项直接将结果赋值给变量,而不需要开启子进程:

Bash 复制代码
# Bash 内建特有:将格式化结果存入变量 my_var
printf -v my_var "Value: %.2f" 3.1415

性能: 在循环中使用内建命令比调用外部文件快得多。

一、Bash 字符串基础

1. 字符串定义方式

bash 复制代码
# 三种定义方式
str1=hello                    # 无引号(不能含空格)
str2='hello world'            # 单引号(原样输出)
str3="hello world"            # 双引号(支持变量/转义)

2. 单引号 vs 双引号

特性 单引号 ' ' 双引号 " "
变量解析 不解析 解析
转义字符 不解析 部分解析
命令替换 不支持 支持
包含单引号 不能 可以
bash 复制代码
name="Alice"

echo 'Hello $name'      # 输出: Hello $name
echo "Hello $name"      # 输出: Hello Alice

echo 'Path: \n'         # 输出: Path: \n
echo "Path: \n"         # 输出: Path: (换行)

echo "Today: $(date)"   # 输出: Today: Mon Jun 9 ...

3. 特殊字符串 $'...'

支持 ANSI-C 转义序列:

bash 复制代码
echo $'Line1\nLine2\tTabbed'
# 输出:
# Line1
# Line2   Tabbed

echo $'It\'s a test'    # 输出: It's a test

二、字符串操作

仅介绍最常用的部分,对完整语法做了简化,详情参考文档或其他资料

  1. 基本格式${parameter}
  2. 间接引用:${!<parameter|prefix<@|*>},其中parameter表示完整变量名或nameref
  3. 变量检索${!<parameter|prefix<@|*>},prefix是变量的一部分(前缀),即根据变量名前缀检索相关变量
  4. 片段截取:${parameter:start[:length]},其中start可以是负整数(此时-号和:必须有空格隔开),0,正整数,length为正整数.
  5. 长度:${#parameter},其中parameter可以是特殊参数@|*或者数组序列array[@]或字符串
  6. 删除:${parameter<%|%%|#|##>pattern}
  7. 替换:${parameter</|//>pattern1/pattern2}
  8. 大小写转换:${parameter<^|^^|,|,,>pattern}
  9. 默认值处理:${parameter[:]<-|=|+|?>word}
  10. 参数变换:${parameter@operator}

1. 基本操作

bash 复制代码
str="Hello World"

# 获取长度
echo ${#str}            # 11

# 字符串拼接
a="Hello"
b="World"
c="$a $b"               # Hello World
c="${a}123"             # Hello123

片段截取

bash 复制代码
# 索引提取(从0开始)
echo ${str:0:5}         # Hello(从位置0取5个字符)
echo ${str:6}           # World(从位置6到末尾)
echo ${str: -5}         # World(取最后5个字符,注意空格)
echo ${str: -5:3}       # Wor(最后5个字符中取前3个)

2. 查找与替换和删除

删除:${parameter<%|%%|#|##>pattern}

替换:${parameter</|//>pattern1/pattern2}

大小写转换:${parameter<^|^^|,|,,>pattern}

bash 复制代码
str="hello.world.txt"

# 删除匹配(# 从左,% 从右)
echo ${str#*.}          # world.txt(删除第一个.及左边)
echo ${str##*.}         # txt(删除最后一个.及左边)
echo ${str%.*}          # hello.world(删除最后一个.及右边)
echo ${str%%.*}         # hello(删除第一个.及右边)

# 替换
echo ${str/world/WORLD} # hello.WORLD.txt(替换第一个)
echo ${str//o/O}        # hellO.wOrld.txt(替换所有)

# 大小写转换 (Bash 4.0+)
str="Hello World"
echo ${str^^}           # HELLO WORLD(全大写)
echo ${str,,}           # hello world(全小写)
echo ${str^}            # Hello World(首字母大写)

3. 默认值处理

默认值处理:${parameter[:]<-|=|+|?>word}

bash 复制代码
# 变量为空时使用默认值
echo ${name:-"Guest"}       # 若name为空,返回Guest

# 变量为空时赋值并返回
echo ${name:="Guest"}       # 若name为空,赋值Guest

# 变量非空时返回替代值
echo ${name:+"has value"}   # 若name非空,返回has value

# 变量为空时报错
echo ${name:?"undefined!"}  # 若name为空,报错退出

三、printf 详解

printf: FORMAT controls the output as in C printf.

完整文档.printf invocation (GNU Coreutils 9.9)

基本语法

bash 复制代码
printf FORMAT [ARGUMENTS...]

🔑 与 echo 区别:printf 不自动换行,格式控制更精确

bash 复制代码
$ help printf
printf: printf [-v var] format [arguments]
    在 FORMAT 的控制下格式化并打印 ARGUMENTS。

    选项:
      -v var    将输出赋值给 shell 变量 VAR,而不是
                显示到标准输出

    FORMAT 是一个字符串,包含三种类型的对象:
    1. 普通字符 - 直接复制到标准输出
    2. 转义序列 - 转换后复制到标准输出
    3. 格式说明符 - 每个说明符导致打印下一个相应的参数

    除了 printf(3) 中描述的标准格式字符 csndiouxXeEfFgGaA 外,
    printf 还支持以下扩展:

      %b        展开对应参数中的反斜杠转义序列
      %q        以可作为 shell 输入重用的方式引用参数
      %Q        类似 %q,但在引用之前先对未引用的参数应用精度限制
      %(fmt)T   使用 FMT 作为 strftime(3) 的格式字符串,
                输出日期时间字符串

    格式字符串会根据需要重复使用,直到消耗完所有参数。
    如果参数少于格式说明符所需的数量,多余的格式说明符
    会被视为提供了零值或空字符串(视情况而定)。

    退出状态:
    除非给出无效选项或发生写入/赋值错误,否则返回成功。

格式字符串FORMAT

FORMAT 是一个字符串,包含三种类型的对象:

  1. 普通字符 - 直接复制到标准输出
  2. 转义序列 - 转换后复制到标准输出
  3. 格式说明符 - 每个说明符导致打印下一个相应的参数

format是printf的核心功能,其本身也是一个字符串 ,也就是大多数情况下printf的参数是一系列字符串,唯一的选项是-v

format将构成printf处理得到最终结果的一部分,formatarguments相互关联,同时也提供了一定的灵活性.

printf格式字符串会根据需要重复使用,直到消耗完所有参数。(处理数组时比较有用,带有循环/遍历的隐式特性)

format串中出现的说明符以 % 开头,它像一个占位符 ,会按顺序"吞掉"后面的参数(argument)并按照指定的规则显示。


形式化描述
bash 复制代码
format_string  ::= ( ordinary_char |  escape_seq |conversion_spec  )*

ordinary_char表示普通字符

escape_seq转移序列

bash 复制代码
escape_seq     ::= '\' ( 'n' | 't' | 'r' | '\\' | '"' | 'x' hex hex | octal{1,3} )

conversion_spec表示格式转换说明符(重点)

转换说明符结构

可以参考K&R <<The C>>中的介绍

bash 复制代码
conversion_spec ::= '%' [flags] [width] ['.' precision] specifier

flags          ::= ( '-' | '+' | ' ' | '#' | '0' )*

width          ::= '*' | positive_integer

precision      ::= '*' | non_negative_integer

specifier      ::= 's' | 'b' | 'q' | 'd' | 'i' | 'u' | 'o' | 'x' | 'X' 
                 | 'f' | 'e' | 'E' | 'g' | 'G' | 'c' | '%'

bash 复制代码
%  [flags]  [width]  [.precision]  specifier
│     │        │          │            │
│     │        │          │            └─ 必须:指定输出类型
│     │        │          └─ 可选:控制精度(片段截取)
│     │        └─ 可选:最小字段宽度
│     └─ 可选:修改输出格式,比如对齐'-',补0
└─ 必须:转换说明起始符
标志总结表
标志 作用 适用类型 示例
- 左对齐 所有(适用于所有类型值的打印) %-10s
+ 显示正负号 数值 %+d
正数前空格 数值 % d
# 替代格式 o,x,X,f,e,g %#x
0 零填充 数值 %08d

这些标志位可以省略,单独使用其中一个,或者组合多个使用.

如果某个标志和声明符不兼容,则会忽略该标志,(比如+%s构成%+s,此时+无效)

bash 复制代码
# 定义格式
fmt_title="|%-14s|%-15s|%08s|%#8s|\n"
fmt="|%-14s|%-+15.2f|%08d|%#8x|\n"
sep="+$(printf '%14s+%15s+%8s+%8s+' | tr ' ' '-')\n"

# 输出表格
printf "$sep"
printf "$fmt_title" "Name" "PriceTrend(\$)" "Code" "IdHex(+)"
printf "$sep"
printf "$fmt" "Apple" "1.50" "100" 16
printf "$fmt" "Banana" "-0.75" "200" 17
printf "$fmt" "Orange" "2.00" "50" 18
printf "$sep"

output

复制代码
+--------------+---------------+--------+--------+
|Name          |PriceTrend($)  |    Code|IdHex(+)|
+--------------+---------------+--------+--------+
|Apple         |+1.50          |00000100|    0x10|
|Banana        |-0.75          |00000200|    0x11|
|Orange        |+2.00          |00000050|    0x12|
+--------------+---------------+--------+--------+

FORMAT串说明

普通字符 (Plain Characters)

这些字符不代表任何特殊指令,printf 会像复印机一样原样输出它们。

Bash 复制代码
# FORMAT 字符串中除了 \n,全是普通字符
printf "Welcome to my server.\n"
# 输出: Welcome to my server.

转义序列 (Escape Sequences)

这些是以反斜杠 \ 开头的特殊符号,用于表示无法直接打出的字符(如换行、回车、制表符)。

Bash 复制代码
# \n 是换行,\t 是制表符(Tab)
str="User:\troot\nStatus:\tOnline\n"
printf "$str"

# output
# User:   root
# Status: Online

printf "%s\n" "$str"
# output: User:\troot\nStatus:\tOnline\n

这里format串包含的\t,\n的字符都被解释成对应的空白字符.

这和printf "%b" "$str"效果相当

第二个例子中,format设置为%s\n(包含格式说明符s),而str是上一个命令的format串,此时输出结果将保留str中的转义字符序列.

这就是说,printf会转义format串中的转义序列,而arguments串中的转义序列是否被解释取决于使用的格式声明符(例如%b启用转义解释,其他情况下不解释,例如%s不解释)

常用转义序列:

  • \n:换行 (Newline)
  • \t:水平制表符 (Tab)
  • \\:反斜杠本身
  • \a:警报声(如果终端支持,会"嘀"一声)

格式说明符 (Format Specifiers)

这是 printf 最强大的地方。说明符以 % 开头,它像一个占位符,会按顺序"吞掉"后面的参数并按照指定的规则显示。

常见说明符案例:

Bash 复制代码
# %s 对应字符串,%d 对应整数,%.2f 对应保留两位的浮点数
printf "Name: %s | ID: %d | Score: %.2f\n" "Alice" 101 95.5
# 输出: Name: Alice | ID: 101 | Score: 95.50

进阶控制(宽度与对齐):

说明符中间可以加入数字来控制显示宽度。

Bash 复制代码
# %-10s 表示左对齐,占用10个字符宽度
# %5d   表示右对齐,占用5个字符宽度
printf "%-10s: [%5d]\n" "CPU" 45
printf "%-10s: [%5d]\n" "Memory" 1024

输出效果:

复制代码
CPU       : [   45]
Memory    : [ 1024]

综合应用:处理数组

当参数的个数超过了说明符的个数时,printf循环使用 FORMAT 字符串,直到处理完所有参数。这在处理 Bash 数组时非常高效:

Bash 复制代码
# 定义一个数组
fruits=("Apple" "Banana" "Cherry")

# FORMAT 只有一个 %s,但后面有三个参数,printf 会自动执行三次
printf "Fruit: %s\n" "${fruits[@]}"

输出结果:

复制代码
Fruit: Apple
Fruit: Banana
Fruit: Cherry

这里"${fruits[@]}" (元素数量大于1)被扩展为 **多个独立的参数(多个字符串对象)**而不是一个字符串整体,被视为参数列表 (List of Arguments),数组fruits内的元素可区分.

"${fruits[@]}" 的类型在 C 语言或底层逻辑中可以理解为 字符指针数组 (char *argv[]) 的展开;而在 Bash 语义中,它就是 多个单词 (Multiple Words)

"${fruits[*]}" 被扩展为 单个字符串对象

bash 复制代码
printf "Fruit: %s\n" "${fruits[*]}"
#output
Fruit: Apple Banana Cherry

标准格式字符(格式说明符)完整列表

字符 描述 示例
%c 单个字符 printf "%c" AA
%s 字符串 printf "%s" hellohello
%n 不打印,存储已输出字符数 见下方示例
%d 有符号十进制整数 printf "%d" 4242
%i %d printf "%i" 4242
%o 无符号八进制(参数为十进制非负整数,下面类似) printf "%o" 810
%u 无符号十进制 printf "%u" 4242
%x 无符号十六进制(小写) printf "%x" 255ff
%X 无符号十六进制(大写) printf "%X" 255FF
%e 科学计数法(小写) printf "%e" 12341.234000e+03
%E 科学计数法(大写) printf "%E" 12341.234000E+03
%f 浮点数 printf "%f" 3.143.140000
%F %f(无穷/NaN大写) printf "%F" 3.143.140000
%g 自动选择 %e%f printf "%g" 0.00010.0001
%G 自动选择 %E%F printf "%G" 12345671.23457E+06
%a 十六进制浮点(小写) printf "%a" 3.140x1.91eb85...p+1
%A 十六进制浮点(大写) printf "%A" 3.140X1.91EB85...P+1

基于上述格式说明符,可以快速实现10进制非负整数转换为进制和十六进制数

由于历史原因,不支持直接输出为二进制表示.

宽度,截断与对齐

bash 复制代码
# 基本宽度(右对齐)
printf "|%10s|\n" "hello"       # |     hello|

# 左对齐(负号)
printf "|%-10s|\n" "hello"      # |hello     |

# 数字宽度与补零
printf "|%5d|\n" 42             # |   42|
printf "|%05d|\n" 42            # |00042|

# 浮点精度
printf "|%10.2f|\n" 3.14159     # |      3.14|
printf "|%-10.2f|\n" 3.14159    # |3.14      |

# 字符串截断
printf "|%.5s|\n" "hello world" # |hello|(只取5字符)
printf "|%10.5s|\n" "hello world" # |     hello|

转义序列

bash 复制代码
printf "Line1\nLine2\n"         # 换行
printf "Col1\tCol2\n"           # 制表符
printf "Alert\a\n"              # 响铃
printf "Back\bspace\n"          # 退格
printf "Over\rwrite\n"          # 回车
printf "Slash \\\\ done\n"      # 反斜杠

-v 选项详解

bash 复制代码
# 将格式化结果赋值给变量(不输出到屏幕)
printf -v greeting "Hello, %s!" "World"
echo "$greeting"    # Hello, World!

# 实用场景:构建复杂字符串
printf -v timestamp "Time: %04d-%02d-%02d" 2024 6 9
echo "$timestamp"   # Time: 2024-06-09

# 在函数中返回格式化结果
format_name() {
    local name=$1
    printf -v "$2" "Mr./Ms. %s" "$name"  # 赋值给第二个参数指定的变量
}
format_name "Smith" result
echo "$result"      # Mr./Ms. Smith

bash中:
printf -v var ...var=$(printf ...)都可以将格式化后的字符串存储到变量var中,但是存在区别.

printf -v var 这种方式直接利用 Bash 的内建功能将结果写入变量。

  • 性能极高: 它在当前的 Shell 进程中直接操作内存,不需要创建子进程。
  • 无管道/子进程开销: 避免了 Fork 进程带来的系统资源消耗。
  • 简洁: 专门用于变量赋值。

var=$(printf ...)这是传统的**命令替换(Command Substitution)**方式。

  • 性能稍慢: Shell 必须 fork 出一个**子进程(Subshell)**来执行 printf,然后通过管道捕获其标准输出,最后再将其赋值给变量。
  • 通用性强: 这种语法在所有的 POSIX Shell(如 sh, dash)中都有效,而 -v 选项是 Bash/Zsh 的特有扩展。
  • 副作用: 命令替换会自动删除结尾的所有换行符 。如果你需要保留字符串末尾的 \n,这种方法不是很好。

扩展格式说明符

%b 格式说明符

展开参数中的转义序列(类似 echo -e):

bash 复制代码
# %s vs %b 对比
str='Hello\tWorld\n'

printf "%s" "$str"    # 输出: Hello\tWorld\n(原样输出)
printf "%b" "$str"    # 输出: Hello   World(解析转义)
                      #       (换行)

# 实用示例
msg='Line1\nLine2\nLine3'
printf "%b\n" "$msg"
# Line1
# Line2
# Line3

%q 格式说明符

生成可安全作为 shell 输入的引用字符串(可重入字符串):

bash 复制代码
# 基本用法
printf "%q\n" "hello world"     # hello\ world
printf "%q\n" "it's a test"     # it\'s\ a\ test
printf "%q\n" 'say "hi"'        # say\ \"hi\"
printf "%q\n" '$HOME'           # \$HOME

# 处理特殊字符
printf "%q\n" "line1
line2"                          # $'line1\nline2'

# 使用场景

#安全传递参数
#创建名字带有空格的文件
args='file with spaces.txt'
echo "demo content" > "$args"
# 计算可用于安全传递的文件名(即便不使用引号包裹)
safe_args=$(printf "%q" "$args")
echo $safe_args #输出 file\ with\ spaces.txt
#构造命令行,并执行
cmd="ls $safe_args"            
# 安全执行
eval "$cmd"

# 生成可重用的命令(生成/导出脚本),使得其他人可以将输出的结果当做命令行来执行
cmd="echo"
arg="Hello 'World'"
printf "%s %q\n" "$cmd" "$arg"  # echo Hello\ \'World\'

%Q 格式说明符(Bash 5.0+)

类似 %q,差别在于配合精度截取(长度截取)的时候

%Q先应用精度限制或字符串截取,再进行引用(结果的长度可能比%q要长,一般的,%q的处理结果是%Q的前缀):

bash 复制代码
str="Hello World"

# %q 应用精度(先引用(转义),再对已引用(转义)的序列结果精度截取(长度截断)
printf "%.7q\n" "$str"    # Hello\

# %Q 应用精度(先截断原始字符串,再引用(添加转义),使得结果字符串包含的字符(因为反斜杠的增加)可能会超出指定的精度(长度)限制)
printf "%.7Q\n" "$str"    # Hello\ W

%(fmt)T 日期时间格式(Bash 4.2+)

使用 strftime 格式输出日期时间:

bash 复制代码
# 特殊值
# 当前时间(-1 或省略参数表示当前时间),效果是格式化输出当前时间
printf "%(%Y-%m-%d)T\n"           # 省略参数也表示当前时间
# 2024-06-09
printf "%(%Y-%m-%d %H:%M:%S)T\n" -1
# 2024-06-09 14:30:25
printf "%(%F %T)T\n" -2    # shell 启动时间

# 使用 Unix 时间戳(根据fmt要求的格式打印)
printf "%(%Y-%m-%d)T\n" 0         # 1970-01-01(epoch)
printf "%(%Y-%m-%d)T\n" 1700000000  # 2023-11-15


# 常用格式代码(截取日期-时间中的某个片段.)
printf "%(%Y)T\n" -1       # 年份: 2024
printf "%(%m)T\n" -1       # 月份: 06
printf "%(%d)T\n" -1       # 日期: 09
printf "%(%H)T\n" -1       # 小时: 14
printf "%(%M)T\n" -1       # 分钟: 30
printf "%(%S)T\n" -1       # 秒: 25
# 其他
printf "%(%A)T\n" -1       # 星期: Sunday
printf "%(%B)T\n" -1       # 月名: June
printf "%(%F)T\n" -1       # 完整日期: 2024-06-09
printf "%(%T)T\n" -1       # 完整时间: 14:30:25
printf "%(%Z)T\n" -1       # 时区: CST

# 替代 date 命令(更高效)
printf -v now "%(%Y%m%d_%H%M%S)T" -1
echo "Backup file: backup_${now}.tar.gz"
# Backup file: backup_20240609_143025.tar.gz

格式重复使用机制(隐式循环)

bash 复制代码
# 参数多于格式说明符时,格式会重复使用
printf "%s\n" apple banana cherry
# apple
# banana
# cherry

printf "%d " 1 2 3 4 5; echo
# 1 2 3 4 5

# 混合格式也会循环
printf "%s=%d\n" name 100 age 25 score 95
# name=100
# age=25
# score=95

# 实用:快速格式化数组
arr=("file1.txt" "file2.txt" "file3.txt")
printf "Processing: %s\n" "${arr[@]}"
# Processing: file1.txt
# Processing: file2.txt
# Processing: file3.txt

参数不足时的行为

主要取决于格式说明符,例如%s则补空串,而%d0,%f0.0

bash 复制代码
# 缺少字符串参数 → 使用空字符串
printf "Name: %s, Age: %s\n" "Alice"
# Name: Alice, Age: 

# 缺少数字参数 → 使用 0
printf "Value: %d, Count: %d\n" 42
# Value: 42, Count: 0

# 缺少浮点参数 → 使用 0.0
printf "Price: %.2f, Tax: %.2f\n" 19.99
# Price: 19.99, Tax: 0.00

实用示例

格式化表格
bash 复制代码
printf "%-15s %8s %10s\n" "Name" "Age" "Salary"
printf "%-15s %8d %10.2f\n" "Alice" 28 5500.50
printf "%-15s %8d %10.2f\n" "Bob" 35 7200.00
printf "%-15s %8d %10.2f\n" "Charlie" 42 8900.75

输出:

复制代码
Name                 Age     Salary
Alice                 28    5500.50
Bob                   35    7200.00
Charlie               42    8900.75
颜色输出
bash 复制代码
# ANSI 颜色代码(使用esc字符转义序列\e或八进制码表示:\033或十六进制\x1b都可以)
RED='\e[0;31m'
GREEN='\033[0;32m'
NC='\x1b[0m' # 恢复默认

printf "${RED}Error Demo msg: %s${NC}(end)\n" "Something failed"
printf "${GREEN}Success: %s${NC}(end)\n" "Operation completed"

echo也可以输出带颜色的文字,使用-e选项

bash 复制代码
echo -e "\e[31m红色文字\e[0m"
生成重复字符
bash 复制代码
# 打印 40 个等号
printf '=%.0s' {1..40}; echo
# ========================================

# 打印分隔线函数
print_line() {
    printf '%*s\n' "${1:-40}" '' | tr ' ' "${2:--}"
}
print_line 30 "="   # ==============================

这里利用printf的隐式循环,利用seq m n来生成数字序列:m,m+1,...,n或者直接使用bash花括号扩展{m..n}

bash 复制代码
f='#%s'
printf "$f" $(seq 1 5) #printf将分别处理输入为1,2,3,4,5的情况
printf "$f" {1..5} 
# output
#1#2#3#4#5

这种情况下,发现每次打印#都会带上一个数字,可以利用%.0s可以丢弃printf的输入而不打印.从而么每次打印都只打印#字符.

进度条
bash 复制代码
fmt="\rProgress: [%-50s] %d%%" #宽度进度条长度50个字符,左对齐
fmt2='=%.0s' # 进度使用'='号填充,其中.0表示每次打印=号时去掉尾随的字符串
for i in {1..100}; do
	# 嵌套printf命令,第一个argument用printf来计算(表示进度条字符串,每次打印指定数量的=标记)
    printf "$fmt" $( printf "$fmt2" $(seq 1  $((i/2))) ) $i #i是偶数的时候打印1个填充标记(一个`=`表示百分2)
    sleep 0.05
done
echo

#output
# Progress: [##################################################] 100%
数字格式化
bash 复制代码
# 千位分隔符(使用 printf + 引号)
printf "%'d\n" 1234567          # 1,234,567 (需LC_NUMERIC支持,git-bash这类环境可能不支持.)

# 十六进制/八进制转换
printf "Dec:%d Hex:%#x Oct:%#o\n" 255 255 255
# Dec:255 Hex:0xff Oct:0377

# 二进制(借助 bc,如果不格式化输出的话,直接用bc即可)
printf "Binary: %s\n" $(echo "obase=2; 255" | bc)
# Binary: 11111111
综合实用示例
bash 复制代码
#!/bin/bash

# 日志函数(带时间戳)
log() {
	# 打印消息前带上打印时间(当前时间,使用-1表示)
    printf "[%(%Y-%m-%d %H:%M:%S)T] %s\n" -1 "$*"
}
log "Script started"
# [2024-06-09 14:30:25] Script started

# 安全构建命令
build_command() {
    local cmd="$1"
    shift
    for arg in "$@"; do
        printf -v cmd "%s %q" "$cmd" "$arg"
    done
    echo "$cmd"
}
build_command ssh user@host "echo 'hello world'"
# ssh user@host echo\ \'hello\ world\'

# 格式化文件大小
format_size() {
	#将输入的字节数(B)转换为合适的单位显示
    local size=$1
    if ((size >= 1073741824)); then
        printf "%.2f GB" "$(echo "scale=2; $size/1073741824" | bc)"
    elif ((size >= 1048576)); then
        printf "%.2f MB" "$(echo "scale=2; $size/1048576" | bc)"
    elif ((size >= 1024)); then
        printf "%.2f KB" "$(echo "scale=2; $size/1024" | bc)"
    else
        printf "%d B" "$size"
    fi
}
format_size 1536000   # 1.46 MB

四、对比总结

特性 echo printf
自动换行
格式控制 有限 强大精确
可移植性 不同shell有差异 POSIX标准,一致性好
转义解析 -e 参数 默认支持
返回值赋值 不支持 printf -v var
bash 复制代码
# printf 赋值给变量
printf -v result "Value: %05d" 42
echo $result    # Value: 00042

五、关于字符串的常见操作

bash 复制代码
#!/bin/bash

# 1. 总是用双引号包裹变量
name="John Doe"
echo "Hello, $name"

# 2. 复杂格式用 printf
printf "User: %-10s ID: %04d\n" "$name" 42

# 3. 多行字符串用 Here Document
cat << EOF
This is line 1
This is line 2 with $name
EOF

# 4. 检查字符串是否为空
if [[ -z "$str" ]]; then
    echo "String is empty"
fi

# 5. 字符串比较用 [[  ]]
if [[ "$str1" == "$str2" ]]; then
    echo "Equal"
fi
相关推荐
xuchaoxin13752 小时前
bash@特殊字符@环境变量符号@特殊参数@参数扩展和替换@字符串处理用法总结
开发语言·bash
dingdingfish2 小时前
Bash 学习 - 第2章:Definitions
bash·definition
xuchaoxin13753 小时前
bash@参数扩展@参数转换@参数扩展操作符
bash
感谢地心引力3 小时前
在Chrome浏览器中使用Gemini,附一键开启方法
前端·chrome·ai·gemini
提娜米苏13 小时前
非Root环境下的数据挂载解决方案:SSHFS与Mount详解
bash·sshfs
John_ToDebug16 小时前
浏览器内核崩溃深度分析:从 MiniDump 堆栈到 BindOnce UAF 机制(未完待续...)
c++·chrome·windows
John_ToDebug18 小时前
WebContent 与 WebView:深入解析浏览器渲染架构的双层设计
c++·chrome·ui
这儿有一堆花1 天前
任何东西都可以转成 Base64!?
bash
wasp5201 天前
Banana Slides 深度解析:AI Core 架构设计与 Prompt 工程实践
人工智能·prompt·bash