bash 教程-5 shell 流程控制 条件判断 重定向 read [MD]


我的GitHub 我的博客 我的微信 我的邮箱
baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目录

目录
  • 目录
  • Bash 教程
    • 流程控制
      • if 条件判断
      • case 条件判断
      • while 循环
      • until 循环
      • for...in 循环
      • for 循环
      • select 循环
    • 用于条件判断的表达式
      • test 命令 [ ] [[ ]]
      • 文件判断 -e -d -f
      • 字符串判断 -n -z >
      • 整数判断 -eq -le -gt
      • 整数的算术表达式 (( ))
      • 逻辑运算符 && ! -a
    • read 命令
      • 读取键盘输入内容
      • 逐行读取文件内容
      • 常用参数
      • 字段分隔符:IFS
    • printf 命令
    • 输入输出重定向
      • 文件描述符
      • 输出重定向 fd>file
      • 输入重定向 fd

Bash 教程

  • bash-tutorial
  • Bash 教程

本文改编自 网道的 Bash 教程,主要为了精简大量本人不感兴趣的内容。

流程控制

if 条件判断

if commands; then
  commands
[elif commands; then
  commands...]
[else
  commands]
fi
  • elifelse 部分是可选的,elif 部分也可以有多个
  • ifthenelifelsefi 命令可以写在同一行,此时各个命令之间需要加分号分隔符
  • if 后面跟一个命令时,如果命令执行成功(即命令的返回值为 0),则代表判断条件成立
  • if 后面跟多个命令时,所有命令都会执行,只要最后一个命令返回 0,就代表判断条件成立
if true; then echo '执行成功'; fi     # true 代表成功
if pwd; then echo '执行成功'; fi      # 如果命令执行成功,则代表判断条件成立
if false; true; then echo '成功'; fi  # 只要最后一个命令执行成功,就代表判断条件成立

case 条件判断

case 结构与其他语言中的 switch ... case 语句类似,用于多值判断时语义更好,可以为每个值指定对应的命令。

case expression in  # expression 是一个表达式
  pattern )         # pattern 是表达式的一个值或一个模式,以右括号结束,中间的空格可省略
    commands ;;     # 每个匹配模式以 ;; 结尾,匹配到一个条件后就会退出 case 结构
  pattern )
    commands ;;     # 高版本中可以用 ;;& 结尾,此时匹配到一个条件后不会退出 case 结构
  *) commands ;;    # 如果无匹配的模式,可在最后使用星号 * 捕获该值
  ...
esac

case 的匹配模式可以使用通配符:

  • a ):匹配单个字符 a
  • a|b ):匹配单个字符 ab
  • [[:alpha:]] ):匹配单个字母
  • ??? ):匹配包含 3 个字符的字符串
  • *.txt ):匹配以 .txt 结尾的字符串
  • * ):匹配任意输入,通常作为 case 结构的最后一个模式,用于匹配其他字符和没有输入字符的情况
case $1 in
  [10-19] )     echo "数字" ;;        # 在匹配一个条件之后,立即退出了 case 结构
  [[:upper:]])  echo "大写" ;;&       # 在匹配一个条件之后,会继续判断下一个条件
  [[:lower:]])  echo "小写" ;;&
  [[:alpha:]])  echo "字母" ;;&
  [[:digit:]])  echo "数字" ;;&
  [[:graph:]])  echo "可见字符" ;;&
  [[:punct:]])  echo "标点符号" ;;&
  [[:space:]])  echo "空格" ;;&
  [[:xdigit:]]) echo "十六进制" ;;&
  * )           echo "其他"
esac

while 循环

  • break:立即终止循环,程序继续执行循环块之后的语句,而不再执行剩下的循环
  • continue:立即终止本轮循环,继续执行下一轮循环
while condition; do  # 只要符合条件,就不断循环执行指定的语句
  commands
done

循环条件 condition 可以是执行一个命令,也可以是一个 test 命令。

while true; do echo '循环'; done             # 按 Ctrl + C 结束
while true; false; do echo '不会执行'; done  # 只看最后一个命令的执行结果
while echo '满足条件'; do echo '循环'; done

number=0
while [ "$number" -lt 10 ]; do
  echo "Number = $number"
  number=$((number + 1))
done

until 循环

until condition; do   # 只要不符合条件,就不断循环执行指定的语句
  commands
done

一般来说,until 用得比较少,只要把条件 condition 设为否定,until 循环都可以转为 while 循环。注意,until 语句极其危险,后面的 condition 语法一旦有误,就会陷入死循环之中。

until false; do echo '循环'; done

number=0
until [ "$number" -ge 10 ]; do
  echo "Number = $number"
  number=$((number + 1))
done

until cp $1 $2; do                 # 在命令执行成功之前,不断重复尝试
  echo '执行失败,5 秒钟后继续尝试'
  sleep 5
done

for...in 循环

for...in 循环用于遍历列表的每一项。

for var in list; do  # 依次从 list 列表中取出一项,作为变量 var
  commands           # 然后在循环体中进行处理
done
for i in a b c; do echo --$i--; done
for i in *.mk; do ls -l $i; done       # 遍历当前目录中所有 .md 文件
for i in $(cat $1); do echo $i; done   # 遍历文件中的每个单词

for i in $@; do echo $i; done   # 遍历脚本的所有参数
for i; do echo $i; done         # 省略 in list 时,list 默认等于脚本的所有参数
  • 在脚本中,省略 for...in 循环的 in list 时,list 默认等于脚本的所有参数
  • 在函数中,省略 for...in 循环的 in list 时,list 默认等于函数的所有参数

for 循环

for (( exp1; exp2; exp3 )); do   # 初始化,循环结束条件,每次迭代后执行
  commands
done

# 等同于下面的 while 循环
(( exp1 ))
while (( exp2 )); do
  commands
  (( exp3 ))
done

圆括号之中使用变量不必加上美元符号 `

for (( i=0; i<5; i=i+1 )); do echo $i; done

for ((;;)); do
  read -p "请输入工号,输入 over 结束 " var
  echo "您输入了 $var"
  if [ "$var" = "over" ]; then break; fi
done

select 循环

select 结构主要用来生成简单的菜单。它的语法与 for...in 循环基本一致。

  • 生成一个菜单,内容是列表 list 的每一项,并且每一项前面还有一个数字编号,格式为 1)
  • 提示用户输入数字编号,按回车完成输入
  • 将用户输入的内容存入环境变量 REPLY,将 REPLY 对应的 list 的内容存入变量 name
  • 如果用户输入了错误的内容,或者没有输入内容直接按回车键,则 name 为空,然后回到第一步
  • 执行命令 commands,执行后回到第一步
select name [in list]; do   # 用户输入以后,将用户输入的内容存入环境变量 REPLY
  commands                  # 将 REPLY 对应的 list 的内容存入变量 name,如果内容不在 list 中,则 name 为空
done

select 结构默认不会结束,直到用户按下 Ctrl + C,或者在程序中出现 break 才会退出执行。

select key in a b c d; do echo "选择了 $REPLY - $key"; date; done    # 默认执行完后不会结束
select key in a b c d; do echo "选择了 $key"; continue; date; done   # continue 仅会结束当次循环
select key in a b c d; do echo "选择了 $key"; break; date; done      # break 可以结束 select 循环
select key in a b c d; do echo "选择了 $key"; exit 0; date; done     # exit 会结束整个脚本的执行

select 可以与 case 结合,针对不同项,执行不同的命令。

echo "选择你喜欢的操作系统"
select os in Ubuntu LinuxMint Windows8 Windows10; do
  case $os in
    "Ubuntu"|"LinuxMint") echo "你喜欢 Linux" ;;
    "Windows8" | "Windows10") echo "你喜欢 Windows" ;;
    *) echo "无效输入"; break ;;                          # 执行完会结束 select 结构
  esac
  echo "选择你喜欢的操作系统"
done

用于条件判断的表达式

以下基本都是 test 命令的语法,test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的判断。

  • $[ ] 作为 算术表达式 时,中括号与内部的表达式之间【不需要有空格】
  • [ ] 作为 条件表达式 时(即 test 命令语法),中括号与内部的表达式之间【必须有空格】

test 命令 [ ] [[ ]]

test 命令有三种形式。

test expression     # 写法一,如果表达式 expression 为真,则表示执行成功,返回值 $? 为 0
[ expression ]      # 写法二,中括号与内部的表达式之间【必须有空格】
[[ expression ]]    # 写法三,中括号与内部的表达式之间【必须有空格】,支持正则判断
[[ str =~ regex ]]  # 如果字符串 str 满足正则表示式 regex,则判断为真
echo $[2+2]         # 【4】做整数运算时,中括号与内部的表达式之间不需要有空格

INT=$1
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then echo "是整数"; exit 0
else echo "不是整数" >&2; exit 1
fi

注意,test 命令内部的 ()<> 等符合需要用引号引起来(或者是用反斜杠转义),否则会被解释

文件判断 -e -d -f

if 关键字后面也可以是下面这些文件判断表达式

建议将文件放在双引号之中,否则文件为空或有空格时,判断会出错

  • [ -a file ]:如果 file 存在,则为true
  • [ -b file ]:如果 file 存在并且是一个块(设备)文件,则为true
  • [ -c file ]:如果 file 存在并且是一个字符(设备)文件,则为true
  • [ -d file ]:如果 file 存在并且是一个目录,则为true
  • [ -e file ]:如果 file 存在,则为true
  • [ -f file ]:如果 file 存在并且是一个普通文件,则为true
  • [ -g file ]:如果 file 存在并且设置了组 ID,则为true
  • [ -G file ]:如果 file 存在并且属于有效的组 ID,则为true
  • [ -h file ]:如果 file 存在并且是符号链接,则为true
  • [ -k file ]:如果 file 存在并且设置了它的 sticky bit,则为true
  • [ -L file ]:如果 file 存在并且是一个符号链接,则为true
  • [ -N file ]:如果 file 存在并且自上次读取后已被修改,则为true
  • [ -O file ]:如果 file 存在并且属于有效的用户 ID,则为true
  • [ -p file ]:如果 file 存在并且是一个命名管道,则为true
  • [ -r file ]:如果 file 存在并且可读(当前用户有可读权限),则为true
  • [ -s file ]:如果 file 存在且其长度大于零,则为true
  • [ -S file ]:如果 file 存在且是一个网络 socket,则为true
  • [ -t fd ]:如果 fd 是一个文件描述符,并且重定向到终端,则为true。可以用来判断是否重定向了标准输入/输出/错误
  • [ -u file ]:如果 file 存在并且设置了 setuid 位,则为true
  • [ -w file ]:如果 file 存在并且可写(当前用户拥有可写权限),则为true
  • [ -x file ]:如果 file 存在并且可执行(有效用户有执行/搜索权限),则为true
  • [ file1 -nt file2 ]:如果 FILE1 比 FILE2 的更新时间最近,或者 FILE1 存在而 FILE2 不存在,则为true
  • [ file1 -ot file2 ]:如果 FILE1 比 FILE2 的更新时间更旧,或者 FILE2 存在而 FILE1 不存在,则为true
  • [ FILE1 -ef FILE2 ]:如果 FILE1 和 FILE2 引用相同的设备和 inode 编号,则为true
FILE=  # 注意:如果 FILE 为空,这时 $FILE 不加双引号时会被判断为真(错误逻辑),放在双引号之中会被判断为假
if [ -e "$FILE" ] || [ -f "$FILE" ] || [ -d "$FILE" ]; then echo "$FILE:条件成立"; fi # 条件成立
if [ -e $FILE ] && [ -f $FILE ] && [ -d $FILE ]; then echo "$FILE:条件不成立"; fi     # 条件不成立

FILE=$1
if [ -e "$FILE" ]; then
  if [ -f "$FILE" ]; then echo "$FILE:存在且是普通文件"; fi
  if [ -d "$FILE" ]; then echo "$FILE:存在且是目录"; fi
  if [ -r "$FILE" ]; then echo "$FILE:存在且可读."; fi
  if [ -w "$FILE" ]; then echo "$FILE:存在且可写."; fi
  if [ -x "$FILE" ]; then echo "$FILE:存在且可执行"; fi
else echo "$FILE:不存在"
fi

字符串判断 -n -z >

if 关键字后面也可以是下面这些字符串判断表达式

建议将字符串放在双引号之中,否则字符串为空或者有空格时,判断会出错

  • [ string ]:如果 string 不为空(长度大于0),则判断为真
  • [ -n string ]:如果字符串 string 的长度大于零,则判断为真
  • [ -z string ]:如果字符串 string 的长度为零,则判断为真
  • [ string1 = string2 ]:如果 string1 和 string2 相同,则判断为真
  • [ string1 == string2 ] 等同于 [ string1 = string2 ]
  • [ string1 != string2 ]:如果 string1 和 string2 不相同,则判断为真
  • [ string1 '>' string2 ]:如果按照字典顺序 string1 排列在 string2 之后,则判断为真
  • [ string1 '<' string2 ]:如果按照字典顺序 string1 排列在 string2 之前,则判断为真
ANSWER=$1
if [ -z "$ANSWER" ]; then echo "字符串为空" >&2; exit 1; fi   # 重定向到标准错误
if [ "$ANSWER" = "yes" ]; then echo "字符串是 YES"
elif [ "$ANSWER" = "no" ]; then echo "字符串是 NO"
else echo "UNKNOWN"
fi

整数判断 -eq -le -gt

if 关键字后面也可以是下面这些整数判断表达式

建议先判断字符串是否为空,否则字符串为空时,判断会出错

  • [ integer1 -eq integer2 ]:如果 integer1 等于 integer2 ,则为 true
  • [ integer1 -ne integer2 ]:如果 integer1 不等于 integer2 ,则为 true
  • [ integer1 -le integer2 ]:如果 integer1 小于或等于 integer2 ,则为 true
  • [ integer1 -lt integer2 ]:如果 integer1 小于 integer2 ,则为 true
  • [ integer1 -ge integer2 ]:如果 integer1 大于或等于 integer2 ,则为 true
  • [ integer1 -gt integer2 ]:如果 integer1 大于 integer2 ,则为 true
INT=$1
if [ -z "$INT" ]; then echo "字符串为空" >&2; exit 1; fi   # 重定向到标准错误

if [ $INT -eq 0 ]; then echo "等于零"
else
  if [ $INT -lt 0 ]; then echo "小于零"; else echo "大于零"; fi
  if [ $((INT % 2)) -eq 0 ]; then echo "偶数 even"; else echo "奇数 odd"; fi
fi

整数的算术表达式 (( ))

if 关键字后面也可以是下面这些整数的算术表达式

  • Bash 提供的算术表达式 ((...)) 可以进行算术运算的判断
  • 如果算术计算的结果是非零值,则表示判断成立。这一点跟命令的返回值正好相反,需要小心
  • 算术条件也可以用于变量赋值,赋值语句返回等号右边的值,如果返回的是非零值,则表示判断成立
if ((3 > 2)); then echo "true"; fi                 # 判断条件为真
if ((1)); then echo "true"; fi                     # 算术计算的结果是非零值,判断条件为真
if ((0)); then echo "true"; else echo "false"; fi  # 算术计算的结果是 0,判断条件为假
if (( foo = 5 )); then echo "foo is $foo"; fi      # 首先把 5 赋值给变量,然后返回 5,判断条件为真
if (( foo = 0 )); then pwd; else echo "false"; fi  # 首先把 0 赋值给变量,然后返回 0,判断条件为假

下面是用算术条件改写的数值判断脚本。

INT=$1
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
  if ((INT == 0)); then echo "等于零"
  else
    if ((INT < 0)); then echo "小于零"; else echo "大于零"; fi
    if (( ((INT % 2)) == 0)); then echo "偶数 even"; else echo "奇数 odd"; fi
fi

逻辑运算符 && ! -a

通过逻辑运算,可以把多个判断表达式结合起来。

  • &&:等价于使用参数 -a
  • ||:等价于使用参数 -o
  • !:使用否定操作符时,最好用圆括号确定转义的范围
mkdir temp && cd temp
[ ! -d temp ] && exit 1      # 如果不是一个目录,则结束
[ -d temp ] || mkdir temp    # 如果不是一个目录,则创建目录
if [ condition1 ] && [ condition2 ] && cd temp; then command; fi
if [ ! \( condition1 -a condition2 \) ]; then echo "满足条件"; fi  # 使用 ! 时,最好用圆括号确定转义的范围

read 命令

读取键盘输入内容

可以使用 read 命令要求用户提供一些数据,并将用户的输入存入变量中。

  • 如果输入项少于 read 命令给出的变量数目,那么多余的变量值为空
  • 如果输入项多于 read 命令给出的变量数目,那么多余的输入项会包含到最后一个变量中
  • 如果 read 命令之后没有定义变量名,那么环境变量 REPLY 会包含输入的一整行数据
echo "请输入姓名和年龄,以空格分隔,按下回车键表示输入结束"
read name age    # 变量名 name age 用来保存输入的数值
echo "姓名:$name 年龄:$age"

逐行读取文件内容

read 命令除了读取键盘输入,还可以用来读取文件。

#!/bin/bash

filename='/etc/hosts'
declare -i line=0  # 声明为整数变量,用于记录行数

while read text    # 使用 read 命令读取文件内容,每次读取一行,并将内容存入变量
do
  echo "$((++line)) $text"  # 打印每行内容
done < $filename            # 定向符 < 用于将文件内容导向 read 命令

上面的命令可以逐行读取文件,每一行存入变量 text,打印出来以后再读取下一行。

常用参数

  • -t:设置超时的秒数。如果超时仍没有输入,脚本将放弃等待,继续向下执行
  • -p:指定用户输入的提示信息,等价于在 read 命令前先执行一个 echo 命令
  • -a:把用户的输入赋值给一个数组
  • -n:限制输入的字符数量(指定只读取若干个字符作为变量值)
  • -e:允许用户输入的时候,使用 readline 库提供的快捷键,比如自动补全
  • -d delimiter:定义使用 delimiter 的第一个字符作为用户输入的结束,而不是换行符
  • -r:raw 模式,表示不把用户输入的反斜杠字符解释为转义字符
  • -s:使得用户的输入不显示在屏幕上,这常常用于输入密码或保密信息
  • -u fd:使用文件描述符 fd 作为输入
read -t 3 text && echo "用户输入了 $text"
read -t 3 text || echo "用户在 3 秒内没有输入内容"

TMOUT=3   # 环境变量 TMOUT 也可以指定 read 命令默认等待超时秒数
read text || echo "用户在 3 秒内没有输入内容"

read -a array && echo ${array[1]}  # 将用户输入被赋值给一个数组,从位置 0 开始
read -n 3 nnn && echo $nnn         # 限制最多输入 3 个字符
read -e file  && echo $file        # 加 -e 参数可以允许用户使用自动补全等快捷键

注意,环境变量 TMOUT 默认是没有设置值的,如果设置的话,也会影响启动的 bash 终端(猜测因为它也是通过 read 命令获取用户输入的),如果在指定的 TMOUT 内没有任何输入,bash 终端也会立即退出。

字段分隔符:IFS

  • read 命令读取的值,默认是以空格分隔
  • 可以通过自定义环境变量 IFS(内部字段分隔符,Internal Field Separator),修改分隔标志
  • IFS 的默认值是空格、Tab 符号、换行符号,通常取第一个(即空格)
  • 如果 IFS 设为空字符串,就等同于将整行读入一个变量
FILE=/etc/passwd
info="$(grep "^$USER:" $FILE)"  # 【bqt:x:1000:1000:,,,:/home/bqt:/bin/bash】

IFS=":" read user pw uid gid name home shell <<< $info
echo $user -  $pw - $uid - $gid - $name - $home - $shell
  • 上面的代码中,将 IFS 设为冒号 :,然后用来分解 /etc/passwd 文件的一行
  • 上面 IFS 的赋值命令和 read 命令写在了一行,则 IFS 的改变仅对此行后面的命令生效,该命令执行后,IFS 会自动恢复原来的值
  • <<< 是 Here 字符串,用于将变量值转为标准输入,因为 read 命令只能解析标准输入

printf 命令

printf 由 POSIX 标准所定义,因此使用 printf 的脚本比使用 echo 移植性好。printf 使用引用文本或空格分隔的参数,外面可以在 printf 中使用格式化字符串,还可以制定字符串的宽度、左右对齐方式等。

  • 格式替代符 %s%c%d%f 分别代表替换字符串、字符、整数、浮点数
  • %-7s:以左对齐的方式,显示至少 7 个字符宽度的字符串
    • 一个中文算 3 个字符、一个英文算 1 个字符
    • 如果宽度不足则自动用空格填充,如果宽度超过也会将内容全部显示出来
    • - 表示左对齐,没有则表示右对齐
  • %-5.2f:以至少 5 个字符宽度、保留 2 位小数的方式,显示数字
    • 小数点算 1 个字符
    • 小数点后的位数需要强制保留,会四舍五入

格式控制字符串

printf "%-7s %-4s" 姓名 性别   # 【姓名  性别】以左对齐的方式,显示至少 7 个字符宽度的字符串
printf "%7s %4s\n" 姓名 性别   # 【 姓名 性别】以右对齐的方式,显示至少 7 个字符宽度的字符串

# 如果格式只指定了一个参数,那么多出的参数仍然会按照该格式输出
printf "%-5.2f-" 1.2 1234.555 # 【1.20 -1234.56-】至少显示 5 个字符宽度,强制保留 2 位小数
printf "%s %s\n" a b c d e    # 【a b(换行)c d(换行)e(换行)】

printf "%s-%d-%c-%f"          # 【-0--0.000000】没有参数列表时的默认值

换行

printf "白乾涛\n"       # 【白乾涛(换行)】----------第一种换行方式:省略参数列表
printf '白乾涛\n'       # 【白乾涛(换行)】格式控制字符串使用单引号、双引号效果一般是一样的
printf 白乾涛\n         # 【白乾涛n】参数列表中有换行时,需要用引号引住

printf "%s\n" "白乾涛"  # 【白乾涛(换行)】---------第二种换行方式:在格式控制字符串中换行
printf "%s\n" "白\n乾"  # 【白\n乾(换行)】有格式控制字符串时,参数列表中的换行符无效
printf %s "白乾涛\n"    # 【白乾涛\n】有格式控制字符串时,参数列表中的换行符无效
printf %s\n "白乾涛"    # 【白乾涛n】格式控制字符串中有换行时,需要用引号引住

printf "%b" "白白\n"    # 【白白(换行)】------------第三种换行方式:使用 %b 在哪都可以换行
printf "%b\n" "白\n白"  # 【白(换行)白(换行)】使用 %b 在哪都可以换行

输入输出重定向

参考

文件描述符

Linux 中 一切皆文件,所有计算机硬件也都是文件。Linux 会给每个文件分配一个整数的 ID,这个 ID 被称为文件描述符(File Descriptor)。Linux 程序在执行任何形式的 I/O 操作时,都是在读写一个文件描述符。一个文件描述符只是一个和打开的文件相关联的整数,它的背后可能是一个硬盘上的普通文件、FIFO、管道、终端、键盘、显示器,甚至是一个网络连接。

一般情况下,每个 Linux 命令运行时都会打开以下三个文件:

  • 文件描述符为 0 的标准输入文件 STDIN,对应的硬件设备为键盘
  • 文件描述符为 1 的标准输出文件 STDOUT,对应的硬件设备为显示器
  • 文件描述符为 2 的标准错误输出文件 STDERR,对应的硬件设备为显示器

Unix 程序默认会从 STDIN 中读取输入信息,并将输出信息写入到 STDOUT,将错误信息写入到 STDERR

输出重定向 fd>file

输出重定向就是改变输出的方向,即不再将命令的结果输出到显示器上,而是输出到其它地方,比如文件中。这样就可以把命令的结果保存起来。

输出重定向语法:command fd>file 或者 command fd>>file

  • fd 表示文件描述符,当 fd 为 1 时(即标准输出),可省略不写
  • > 代表覆盖,>> 代表追加
  • 注意:> 和 fd 中间 不可以 有空格,> 和 file 中间的空格可有可无

标准输出、标准错误输出重定向

  • command >file:以覆盖的方式,把 command 的正确输出结果输出到 file 文件中
  • command >>file:以追加的方式,把 command 的正确输出结果输出到 file 文件中
  • command 2>file:以覆盖的方式,把 command 的错误信息输出到 file 文件中
  • command 2>>file:以追加的方式,把 command 的错误信息输出到 file 文件中
file=outFile.txt
echo "aaa" >$file && cat $file   # 将命令执行结果写入到文件中,会覆盖文件内容
echo "bbb" >>$file && cat $file  # 将命令执行结果追加到文件后面,不会覆盖文件内容

# 注意:命令正确执行时是没有错误信息的,另外执行出错后必须使用 || 才会执行同一行的其他命令
xxx 2>$file || cat $file         # 将命令 xxx 不存在的错误信息,以覆盖方式写入文件中
ls xxx . 2>>$file || cat $file   # 将执行 ls 时找不到文件的错误信息,追加写入到文件中

标准输出、标准错误输出同时重定向

  • command >file 2>&1:以覆盖的方式,把正确输出和错误信息同时保存到文件 file 中
  • command >>file 2>&1:以追加的方式...
  • command >file1 2>file2:把正确的输出结果输出到 file1 中,把错误信息输出到 file2 中
  • command >>file1 2>>file2:以追加的方式...
  • command >file 2>file:【不推荐】stdout 和 stderr 会互相覆盖
  • command >>file 2>>file:【不推荐】stdout 和 stderr 会互相覆盖
file1=out.txt
file2=error.txt
ls xxx . 2>$file1          # 把正确结果和错误信息都保存到文件 file1 中
ls xxx . >$file1 2>$file2  # 把正确的输出结果输出到 file1 中,把错误信息输出到 file2 中

重定向 /dev/null 文件

如果你既不想把命令的输出结果保存到文件,也不想把命令的输出结果显示到屏幕上,那么可以把命令的所有结果重定向到文件 /dev/null 中。这是一个特殊的文件,写入到它的内容都会被丢弃,并且不能恢复。如果尝试从该文件读取内容,那么什么也读不到

ls xxx . >/dev/null
ls xxx . &>/dev/null
ls xxx . >/dev/null 2>&1

输入重定向 fd

输入重定向就是改变输入的方向,即不再使用键盘作为命令输入的来源,而是使用文件作为命令的输入。

  • command :将 file 文件中的内容作为 command 的输入
    • 和输出重定向类似,输入重定向的完整写法是 fd
    • 当文件描述符 fd 为 0 时(即标准输入),可省略不写
  • command file2:将 file1 作为 command 的输入,并将 command 的处理结果输出到 file2
  • command <:从标准输入中读取数据,直到遇见分界符 END 才停止,其实就是之前提到的 Here Document
in=inFile.txt
out=outFile.txt
while read str; do echo "-- $str"; done <$in  # 逐行读取文件内容

# wc 命令可以用来对文本进行统计,包括单词个数(-w)、行数(-l)、字节数(-c)
wc -l $in       # 【2 inFile.txt】统计指定文件的行数,这里会打印文件名
wc -l <$in      # 【2】统计标准输入文件 STDIN 的行数,这里不会打印文件名

cat <$in >$out  # 从 in 中读取内容,然后将输出写入到 out 中
cat << EOF
    也可以将 Here Document 用在脚本中
这里的格式会被保留的,例如前面的空格或 Tab 符
EOF

2022-1-22