Bash 学习笔记

终端快捷键

  • ⌃R:搜索历史命令
  • ⌃D: 若在新行输入,表示 EOF。若在行中输入,表示输出 stdin 的缓冲区。此时连按两下 ⌃D 表示 EOF
  • ⌃Z: 挂起,可用 fg 命令恢复
  • ⌃C: 终止进程
  • ⌃V: 字面值输入

执行 Shell 脚本

以 #! 开头的语句叫做 Shebang,用于指定脚本解释器。

不推荐:

#!/bin/bash

推荐:

#!/bin/env bash

在新进程中运行 Shell 脚本

# 将 Shell 脚本作为程序运行
chmod +x script.sh  # 给脚本添加执行权限
./script.sh         # 运行脚本

# 将 Shell 脚本作为参数传递给 Bash 解释器
bash script.sh      # 这种方式将忽略脚本文件第一行的指定解释器信息

在当前进程中运行 Shell 脚本

source ./script.sh
. ./script.sh       # source 命令的简化写法

进程替换

进程替换(Process Substitution)将一个命令的输出作为一个文件来使用,而不需要创建一个实际的临时文件。通常用于将命令的输出传递给只接受文件作为输入的命令。

语法:

<(some command)

<( ) 会将括号中的命令放入后台执行,并将其输出连接到一个命名管道(FIFO)或某个伪文件(在一些系统中表现为 /dev/fd/n),然后将这个伪文件的路径传递给外部命令。

例:

diff <(ls dir1) <(ls dir2)

在这个例子中,ls dir1 和 ls dir2 的输出分别通过 <( ) 语法被当作文件传递给 diff 命令进行比较。

Bash 配置文件

Shell 有多个配置文件。对普通用户来说,~/.bashrc 是最重要的文件,因为不管是否登陆都会加载该文件。

PS:在某些情况下并不一定加载 ~/.bashrc。曾经使用学校超算时遇见过这样一个节点,使用 SSH 登录后只会自动加载 ~/.profile 文件,而不会加载 ~/.bashrc。

在 /etc/profile.d 下的所有 .sh 文件以及 /etc/profile 文件等价于全局的用户 ~/.profile 文件,会在每次登录时执行,而在 ~/.bash_profile 下的脚本只会在第一次登录时执行。

Git Bash for Windows 的配置文件位置在 C:\Program Files\Git\etc\profile.d\git-prompt.sh

flowchart TD X(登录) --> A0[登录 Shell] A0 --> A1["/etc/profile"] --> A2["/etc/profile.d"] A2 --> A3["~/.bash_profile"] A3 --> A4["~/.bash_login"] A4 --> A5["~/.profile"] A5 --> Y["~/.bashrc"] X --> B0["交互式非登录 Shell"] B0 -------> Y classDef c1 fill:#afa,stroke:#080; class A0,B0 c1; classDef c2 fill:#f96,stroke:#f66; class X,Y c2;

修正:在 Ubuntu 中似乎 SSH 登录 shell 默认执行 ~/.bash_profile 而不是 ~/.bashrc

参考:Shell 配置文件(配置脚本)的加载 | C 语言中文网

示例:

# 给 PATH 变量增加新的路径
PATH="$PATH:$HOME/addon"  # 将主目录下的 addon 目录也设置为系统路径
# 修改命令提示符的格式
PS1="[bash]\$"            # 命令提示符改为 [bash]$

PATH 使用冒号 : 分隔不同的路径

参见:PS1 和 PS2 中的特殊字符 | C 语言中文网

变量

在 Bash shell 中,每一个变量的值都是字符串。在默认情况下,Bash shell 不会区分变量类型。当然,如果有必要,也可以使用 declare 关键字显式定义变量的类型。

# 定义变量
variable=value            # 如果要赋的值不包含任何空白符及转义字符,那么可以不使用引号。不建议这种写法
variable='value'          # '' 相当于原生字符串,将不解析内容中的变量、命令
variable="value"          # 最常用的情况。会解析其中的变量,命令。但是不会解析转义字符

readonly variable         # 将变量定义为只读变量
unset variable            # 删除变量。unset 命令不能删除只读变量

# 使用变量
author="John"
echo $author
echo "作者是 ${author}"  # 用花括号指明变量名的边界

⚠️ 注意: 不要使用 path 作为变量名,在大小写不敏感的系统中 path 等价于环境变量 PATH 。

Globbing

Bash 会在没有被引号包围的命令行参数上应用文件名扩展。

Wildcard(通配符):

  • *:表示任意长度的字符串(不匹配 . 开始的文件名和包含路径分隔符 / 的文件名)
  • ?:表示一个字符

假设当前目录下有文件 file1.txt, file2.txt, ...,file9.txt,以及 filea.txt,fileb.txt,filec.txt,那么:

file[135].txt        # 单字符匹配。匹配 file1.txt, file3.txt, file5.txt
file[1-9].txt        # 范围匹配。匹配 file1.txt, file2.txt, ..., file9.txt
file[!1].txt         # 否定匹配。匹配 file2.txt, file3.txt, ..., file9.txt
file[1-3a-c].txt     # 复合集合。匹配 file1.txt, file2.txt, file3.txt, filea.txt, fileb.txt, filec.txt
file[[:digit:]].txt  # 字符类,匹配 file1.txt, file2.txt, ..., file9.txt

常见的字符类包括 [:alpha:]、[:alnum:]、[:lower:]、[:upper:] 等。

说明:当使用方括号匹配时,如果模式不匹配任何文件或目录,它的行为将取决于 shell 的设置和选项(例如,Zsh 中的 nullglob 和 nomatch 选项影响这种行为)。

花括号扩展

格式:prefix{suf1,suf2}

花括号扩展可以将花括号中的 suf1 和 suf2 依次与 prefix 结合生成一组字符串。

例:

mv file{,.bak}       # mv file file.bak
cat file{1,2,3}.txt  # cat file1.txt file2.txt file3.txt

参数展开

参数展开(Parameter Expansion)允许你对变量进行一些操作。语法为 ${...}。

例:

var=${var:-new}      # 如果 var 为空或未定义,那么将 var 替换为 new(常用)
var=${var:+new}      # 如果 var 不为空,那么将 var 替换为 new
var=${var/old/new}   # 将 var 中的第一个 old 替换为 new
var=${var//old/new}  # 将 var 中的所有 old 替换为 new
var=${var/#old/new}  # 如果 var 的前缀匹配 old,那么将匹配的 old 替换为 new
var=${var/%old/new}  # 如果 var 的后缀匹配 old,那么将匹配的 old 替换为 new

参数展开修饰符

参数展开修饰符(Parameter Expansion Flags)是 Zsh 提供的一种扩展参数展开功能。

${(o)array}  # 对数组排序
修饰符 含义
L 将字符串转换为小写
U 将字符串转换为大写
f 将字符串按换行符分割为数组
o 数组按字典升序排序
O 数组按字典降序排序
n 数组按数值升序排序
k 展开关联数组的键
v 展开关联数组的值
@ 当数组被引号包围时,@ 确保每个元素被当作独立的个体处理

参考:参数替换 | 简书

命令替换

将命令的结果赋给变量

var=`cat log.txt`                            # 如果有多个命令,命令之间以分号 ; 分隔
var=$(cat log.txt)                           # 更推荐的写法
Fir_File_Lines=$(wc -l $(ls | sed -n '1p'))  # 可以嵌套
echo "$var"  # 如果被替换的命令的输出内容有换行符,或者含有多个连续的空白符,那么在输出变量时应该将变量用双引号包围,否则系统会使用默认的空白符来填充。

修改变量的值时不能在变量名前加 $,只有在使用变量时才能加 $。

= 的周围不能有空格

变量的作用域

  • 局部变量:

    function func() {
      local a=99  # 若不使用 local,则 a 是全局变量
    }
    
  • 全局变量:

    在当前 Shell 进程中有效。在 Shell 中定义的变量默认就是全局变量。

  • 环境变量:

    在所有的子进程中有效。

    export a     # 将变量导出为环境变量
    export b=22  # 在定义的同时导出为环境变量
    

    环境变量被创建时所处的 Shell 进程称为父进程,父进程中创建的进程叫子进程。创建子进程最简单的方式是运行 bash 命令。通过 exit 命令可以一层一层地退出 Shell。

    exit 可以返回命令的退出状态:exit 0

    image

    参见:How To Read and Set Environmental and Shell Variables on Linux | DigitalOcean

特殊变量

变量 含义
$0 脚本名称
$1 位置参数
$# 位置参数的个数
$* 所有位置参数的组合
$@ 所有位置参数的数组
$? 上一个命令的退出状态
$$ 当前 Shell 的 PID
$! 最后一个后台进程的 PID

$1 是一种宏替换

命令的退出状态由 exit 命令返回,函数的返回值由 return 返回

$* 和 $@ 的区别

当 $* 和 $@ 不被双引号 "" 包含时,它们没有任何区别。

当它们被双引号 "" 包含时,会有如下区别:

  • "$*" 会将所有的参数从整体上看做一份数据,而不是把每个参数都看做一份数据。
  • "$@" 仍然将每个参数都看作一份数据,彼此之间是独立的。(常用)

位置参数

Shell 脚本文件和函数可以自动接收传入的参数,并以 $1, $2, ... 的方式表示。

如果参数个数达到了 10 个,那么需要用 ${10} 的方式指明参数名的边界

  • 给脚本文件传递位置参数:

    script.sh:

    #!/bin/bash
    echo "Language: $1"
    echo "Name: $2"
    
    $ source script.sh Shell XiaoMing
    Language: Shell
    Name: XiaoMing
    
  • 给函数传递位置参数

    $ function func() {
    $   echo "Language: $1"
    $   echo "Name: $2"
    $ }
    $ func C++ XiaoMing
    Language: C++
    Name: XiaoMing
    

字符串

# 字符串拼接
str1="a"
str2="b"
str3=$str1$str2"c"  # 只需将字符串放在一起就能拼接

# 获取字符串长度
echo ${#string}  # 6

字符串截取

截取范围:

# 从左边开始计数
$ str=111a222b333a444  # a 是第 3 位(从 0 开始计数)
$ echo ${str:3:5}      # 从第 3 位开始输出 5 位
a222b
$ echo ${str:3}        # 从第 3 位开始输出到末尾
a222b333a444
# 从右边开始计数
$ echo ${str:0-4:5}    # 从倒数第 4 位开始输出 4 位。b 是倒数第 4 位(从 1 开始计数)
b333a

截去子串:

# 截去左边(截去前缀)
$ echo ${str#*a}    # 从左边截去第一个匹配 a 及之前的所有字符
222b333a444
$ echo ${str#111a}  # 省略通配符 *,此时需要写出完整的前缀
222b333a444
$ echo ${str##*a}   # 从右边截去第一个匹配 a 及之前的所有字符
444

# 截去右边(截去后缀)
$ echo ${str%a*}    # 从右边截去第一个匹配 a 及之后的所有字符
111a222b333
$ echo ${str%%a*}   # 从左边截去第一个匹配 a 及之后的所有字符
111

数组

数组的声明与使用

在 Zsh 中,数组索引从 1 开始计数;在 Bash 中,数组索引从 0 开始计数。

在 Shell 中,用 () 来表示数组,数组元素之间用空格分隔:

# 数组没有长度限制
nums=(1 2 3)        # 定义一个数组
nums=(1 2 3 "abc")  # 数组没有类型限制
nums+=(4 5 6)       # 在数组末尾添加元素
nums[20]=88         # 可以只给特定元素赋值
nums=([3]=24 [5]=19 [10]=12)

# 获取数组元素
echo ${nums[0]}     # 输出第 0 个元素:1(Bash)
echo ${nums[*]}     # 输出数组所有元素
echo ${nums[@]}     # 另一种写法

unset nums[0]       # 删除数组元素
unset nums          # 删除数组

⚠️ Zsh 中数组的行为与 Bash 有所不同:

array=(1 2 3)

echo $array       # Bash:输出第 0 个元素;Zsh:输出所有元素
echo ${array[1]}  # Bash:1;Zsh:2
echo $array[1]    # 只有 Zsh 可以省略 {}

获取数组长度

和获取字符串长度的方法类似,使用 # 号:

# 两种形式是等价的
echo ${#nums[*]}
echo ${#nums[@]}

数组拼接

先将数组展开,然后再合并到一起:

# 两种形式是等价的
arr=(${nums_1[*]} ${nums_2[*]})
arr=(${nums_1[@]} ${nums_2[@]})

关联数组

也即 Python 中的字典

  • 定义与使用

    declare -A color  # 定义一个名为 color 的关联数组
    color["red"]="#ff0000"
    color["green"]="#00ff00"
    color["blue"]="#0000ff"
    
    # 在定义的同时赋值
    declare -A color=(
      ["red"]="#ff0000"
      ["green"]="#00ff00"
      ["blue"]="#0000ff"
    )
    
    echo $(color["red"])  # 访问关联数组
    
  • 操作关联数组

    Bash:

    # 获取所有键
    # 两种形式是等价的
    ${!color[*]}
    ${!color[@]}
    

    Zsh:

    # 获取所有键
    ${(k)color}
    "${(@k)color}"  # @ 使得每个键作为独立个体存在,这种用法最常用
    
    # 遍历所有键和值
    for key val in ${(kv)color}; do
        echo "$key: $val"
    done
    

declare 命令

语法:

declare [-/+] [选项] expression

declare 和 typeset 都是 Shell 内建命令,它们用法相同,不过 typeset 已经弃用。

- 表示设置属性,+ 表示取消属性。

选项 含义
-f 列出之前由用户在脚本中定义的函数名称和函数体
-F 仅列出自定义函数名称
-g 在 Shell 函数内部创建全局变量
-p 显示指定变量的属性和值
-a 声明变量为普通数组
-A 声明变量为关联数组
-i 将变量定义为整型
-r 将变量定义为只读
-x 将变量设置为环境变量

例:

$ declare -i n m num  # 将 n, m, num 声明为整型
$ declare -r CONST=23 # 将 CONST 声明为只读,等价于 readonly CONST

$ declare -p CONST    # 显示变量的属性和值
declare -r CONST="23"

结构语句

if else

if cond; then
  echo "true"
else
  echo "false"
fi

💡 Tip: if 检测的是 cond 命令的退出状态。通常退出状态为 0 表示 “成功” ,其他状态表示 “失败” 。例如,((1)) 的退出状态为 0 。

if elif else

if cond1; then
  echo "cond1"
elif cond2; then
  echo "cond2"
elif cond3; then
  echo "cond3"
else
  echo "false"
fi

if 语句也支持使用逻辑运算符将多个退出状态组合起来。

case in

语法:

case expression in  # expression 可以是变量、数学表达式,或者是命令的执行结果。只要能得到 expression 的值就可以
  pattern1)         # pattern 可以是数字,字符串,甚至是简单的正则表达式
    echo "pattern1"
    ;;              # 两个分号表示一条 case 语句的结束
  pattern2)
    echo "pattern2"
    ;;
  pattern3)
    echo "pattern3"
    ;;
  *)                # 可以没有 *) 部分。* 实际上是正则表达式
    echo "default"  # 最后的双分号可以省略
esac

case in 支持的正则表达式

格式 说明
* 表示任意字符串
[abc] 表示 a, b, c 三个字符中的任意一个。比如 [15ZH] 表示 1, 5, Z, H 中的任意一个
[m-n] 表示从 m 到 n 的任意一个字符。比如 [0-9] 表示一个数字,[0-9a-zA-Z] 表示一个字母或数字
| 表示多重选择,相当于或运算。比如 abc|xyz 表示匹配字符串 "abc" 或 "xyz"

例:

read -n 1 char
case $char in
  [a-zA-Z])
    printf "\n字母\n"
    ;;
  [0-9])
    printf "\n数字\n"
    ;;
  [,.?!])
    printf "\n标点符号\n"
    ;;
  *)
    printf "\n错误\n"
esac

select in

select in 可以自动显示带编号的菜单,用户输入编号就可以为循环变量赋予编号对应的值。select in 经常与 case in 一起使用。

语法(结合 case in):

select variable in value_list; do
  case variable in
    pattern1)
      echo "pattern1"
      break        # select in 只有遇到 break 或文件结束符才会退出循环
      ;;
    pattern2)
      echo "pattern2"
      break
      ;;
    pattern3)
      echo "pattern3"
      break
      ;;
    *)
      echo "输入错误,请重新输入。"
  esac
done

可以修改环境变量 PS3 来自定义输入提示符

break,continue

break n
continue n

其中 n 表示作用的循环的层数(从内向外)。

while

语法:

while condition; do
  statements
done

until

until 的使用场景很少,一般使用 while 即可。

语法:

until condition; do  # 若 condition 不成立则执行循环语句
  statements
done

for

C 风格 for

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

Python 风格 for in(常用)

for variable in value_list; do
  statements
done

value_list

  1. 直接给出具体的值:1 2 3 4 5,"john" "jack" "tom"
  2. 给出一个取值范围(只支持数字和字母):{1..5},{a..f}
  3. 使用命令的执行结果:$(ls)
  4. 使用 Shell 通配符:*.sh
  5. 使用特殊变量:$@(如果省略 value_list 的话相当于使用 $@)

参见:Shell 通配符

[[#特殊变量]]

函数

语法:

# 定义
function func() {  # function 关键字可以省略。如果写了 function 关键字,那么 () 可以省略
  statements
  [return value]
}

function func {  # 省略 ()
  ...
}

func() {  # 省略 function
  ...
}

function func --description "some description"  # 你可以为函数添加描述

# 调用
func [param1] [param2] [...] # 函数名后面不加括号

Shell 不限制定义和调用的顺序。

函数返回值

Shell 中的函数返回值是一个介于 0~255 的整数,用来表示函数的退出状态:返回值为 0 表示函数执行成功,非 0 表示函数执行失败。函数执行失败时可以根据退出状态来判断具体出现了什么错误。

💡 Tip: 在 Shell 中不要用返回值表示函数的处理结果

如果函数体中没有 return 语句,那么函数将使用最后一条命令的退出状态作为自己的返回值。

如何得到函数的处理结果

要想得到函数的处理结果,可以使用以下两种方案:

  • 在函数内部使用 echo,printf 等命令将结果输出,并在函数外部使用 $() 或 `` 捕获结果。(推荐)
  • 将处理结果赋给全局变量

条件表达式

[[ ]] 用来检测某个条件是否成立。

[[ ]] 是 Shell 内置关键字,不是命令,在使用时没有给函数传递参数的过程。因此相比 test 命令,[[ ]] 不需要关注某些细枝末节:

  • 不需要把变量名用双引号 "" 包围起来。即使变量是空值,也不会出错。
  • 不需要对 >、< 进行转义。

语法:

[[ expression ]]  # 注意两侧的空格

文件检测操作符

expression 作用
-e path 判断 path 是否存在
-d path 判断 path 是否存在,并且是否为目录文件
-f path 判断 path 是否存在,并且是否为普通文件
-s path 判断 path 是否存在,并且是否非空
-r path 判断 path 是否存在,并且是否拥有读权限
-w path 判断 path 是否存在,并且是否拥有读权限
-x path 判断 path 是否存在,并且是否拥有执行权限
path1 -nt path2 判断 path1 的修改时间是否比 path2 新
path1 -ot path2 判断 path1 的修改时间是否比 path2 旧
read fileName
read msg

if [[ -w $fileName ]] && [[ -n $msg ]]; then
  echo $msg >$fileName
  echo "写入成功"
else
  echo "写入失败"
fi

使用 ! 的情况:

if [[ ! -e $path ]]; then
  echo "$path 不存在"
fi

# 不要写成下面这样
if ![[ -e $path ]]
# 或者
if [[ !(-e $path) ]]

字符串判断操作符

expression 作用
-z str 判断 str 是否为空
-n str 判断 str 是否非空
str1 == str2 判断 str1 是否和 str2 相等
str1 != str2 判断 str1 是否和 str2 不相等
str1 > str2 判断 str1 是否大于 str2
str1 < str2 判断 str1 是否小于 str2

💡注意:在使用 =、==、!=、>、< 运算符比较字符串时,运算符与两边的字符串之间一定要有空格!否则可能会出现奇葩问题。

read str1
read str2

# 检测字符串是否为空
# 条件表达式支持逻辑运算符
if [[ -z $str1 || -z $str2 ]]; then
  echo "字符串不能为空"
# 比较字符串
elif [[ $str1 < $str2 ]]; then
  echo "str1 < str2"
else
  echo "str1 >= str2"
fi

条件表达式支持正则表达式

可以使用 =~ 来检测字符串是否符合某个正则表达式:

# 检测一个字符串是否是手机号
read tel
if [[ $tel =~ ^1[0-9]{10}$ ]]; then
  echo "你输入了一个手机号"
else
  echo "你输入的不是手机号"
fi

更多操作符请参见 Shell test 命令(Shell [])详解,附带所有选项及说明 | C 语言中文网

数学运算

除了 C 语言中包含的所有算数运算符外,Shell 还包括幂运算符:**

整数运算

将表达式写在双小括号之间:((1+2))

多个表达式用 , 分隔,最后一个表达式的值将作为命令的执行结果。

使用 $ 获取 (( )) 命令的结果。

Shell 中还有 let 命令,$[] 命令,它们的功能和 (()) 一样,都是计算整数。 除此之外,还有 expr 命令也可以用于整数计算。

例:

$ ((a=2-1))
$ ((b=a+4))
$ echo $((1+2, 2**3))
8
$ echo $((a<2&&b==5))
1
$ echo $((a++))
1

浮点数运算

使用 bc 命令

一行一个表达式,或者一行中多个表达式用分号 ; 分开。

quit 退出

内置变量:

变量名 作用
scale 指定精度,默认为 0
ibase 指定输入进制
obase 指定输出进制
last 或 . 表示最近打印的数字

💡 Tip: obase 要尽量放在 ibase 前面,因为指定 ibase 后,所有后续输入的数字都是以 ibase 的进制来换算的。

内置函数:

在输入 bc 命令时需要使用 -l 选项启用数学库。

函数名 作用
s(x) 正弦函数,x 是弧度值
c(x) 余弦函数,x 是弧度值
a(x) 反正切函数,返回弧度值
l(x) 自然对数函数
e(x) 自然指数函数
j(n,x) 贝塞尔函数,计算从 n 到 x 的阶数

在 Shell 中使用 bc 计算器:

echo "expression" | bc  # expression 可以包含 Shell 中的变量:$var
var=$(echo "expression" | bc)

例:进制转换

# 10 进制转 16 进制
i=15
j=$(echo "obase=16;$i" | bc)
echo $j

# 16 进制转 10 进制
k=$(echo "obase=10;ibase=16;$j" | bc)  # 别忘了 obase 要写在 ibase 前面
echo $k

借助重定向使用 bc 计算器:

var=$(bc << EOF  # 分界符(EOF)可以自定义
2*3
last/2
EOF

这种方式在有大量数学计算时比较方便

参考:Linux bc 命令 | C 语言中文网

重定向

文件描述符

为了表示并区分已经打开的文件,Linux 会给每个文件分配一个 ID,即文件描述符(File Descriptor)。stdin,stdout,stderr 是默认打开的,它们已经有了自己的文件描述符:

FD 文件名
0 stdin
1 stdout
2 stderr

Linux 始终从文件描述符 0 读取内容,向文件描述符 1 输出正确结果,向文件描述符 2 输出错误提示。

文件描述符操作符(>, <)可以修改文件描述符指向的文件,从而实现重定向的功能。

输出重定向

格式:FD>file

在输出重定向中,> 表示覆盖,>> 表示追加。

💡 Tip: FD 和 > 之间不能有空格,否则 Shell 会解析失败;> 和 file 之间的空格可有可无。

command >>file           # 以追加输出的方式打开 file,并让文件描述符 1 指向 file。这里 1 被省略
command 2>>file
command >>file1 2>>file2 # 正确结果输出到 file1,错误提示输出到 file2
command >>file 2>&1      # 2>&1:让文件描述符 2 指向文件描述符 1 所指的文件。结果是 1 和 2 都指向 file
command &>file           # 以覆写输出的方式打开 file,并让文件描述符 1 和 2 都指向 file
command n>&-             # 关闭文件描述符 n 及其代表的文件。n 默认为 1

建议将正确结果和错误输出保存到不同文件

/dev/null 文件

如果不想显示或保存任何输出,可以将输出重定向到 /dev/null 文件中:

ls > /dev/null   # 仅抛弃标准输出
ls 2> /dev/null  # 仅抛弃错误输出
ls &> /dev/null  # 抛弃所有输出

CMD:

REM 仅抛弃标准输出
dir > NUL
REM 仅抛弃错误输出
dir 2>NUL
REM 抛弃所有输出
dir > NUL 2>&1

PowerShell:

Get-ChildItem | Out-Null   # 仅抛弃标准输出

Get-ChildItem > $null      # 仅抛弃标准输出
Get-ChildItem 2> $null     # 仅抛弃错误输出
Get-ChildItem *> $null     # 抛弃所有输出

输入重定向

格式:FD<file

command <file          # 以输入的方式打开 file,并让文件描述符 0 指向 file。这里 0 被省略
command <input >output # 从 input 输入,正确结果输出到 output
command n<>file        # 同时以输入和输出的方式打开 file,并让文件描述符 n 指向 file。n 默认为 0

# 使用 Here Document
command <<EOF
  document
EOF

Here Document

语法:

# END 是分界符,可以自定义
command <<END
  document
END
# END 行内不能有其他内容

Shell 将把 document 的内容输入到命令中。

忽略命令替换

默认情况下,document 中出现的变量和命令也会被求值或运行。你可以将分界符用单引号或双引号包围来使 Shell 替换失效:

command <<'END'
  document
END

忽略制表符

默认情况下,行首的制表符也被当做正文的一部分。不过我们输入制表符仅仅是为了格式对齐,并不希望它作为正文的一部分。为了去掉制表符,你可以在 << 和 END 之间添加 -:

cat <<-END
  document
END

常见用法

Here Document 最常用的功能还是向用户显示命令或者脚本的用法信息:

usage(){
  cat <<-END
    usage: command [-x] [-v] [-z] [file ...]
    A short explanation of the operation goes here.
    It might be a few lines long, but shouldn't be excessive.
END
}

代码块重定向

将重定向命令放在代码块的结尾处,就可以对代码块中的所有命令实施重定向:

sum=0
while read n; do
  ((sum += n))
done <input >output  # 输入重定向为 input,输出重定向为 output 。

# 组命令重定向
{
  echo "line 1"
  echo "line 2"
  echo "line 3"
} >file

管道

command1 | command2  # 管道符 | 左边命令的 stdout 将连接到右边命令的 stdin

注意管道只处理正确的输出(stdout),如果想让管道也处理错误输出(stderr),需要将 stderr 重定向到 stdout: command1 2>&1 | command2

管道数据流指示

curl -sSL https://install.python-poetry.org | python3 -  # 从网络获取安装脚本,然后执行

python3 - 表示 Python 将从 stdin 读取输入

管道与输入重定向

command1 <input | command2 | command3 >output  # 第一个命令从 input 获取输入,最后一个命令向 output 写入输出。

C 语言中文网:管道详解

过滤器

过滤器:从标准输入读取数据,向标准输出输出结果的命令

常用过滤器:

命令 说明
awk 用于文本处理的解释性程序设计语言,通常被作为数据提取和报告的工具。
cut 用于将每个输入文件(如果没有指定文件则为标准输入)的每行的指定部分输出到标准输出。
grep 用于搜索一个或多个文件中匹配指定模式的行。
tar 用于归档文件的应用程序。
head 用于读取文件的开头部分(默认是 10 行)。如果没有指定文件,则从标准输入读取。
paste 用于合并文件的行。
sed 用于过滤和转换文本的流编辑器。
sort 用于对文本文件的行进行排序。
split 用于将文件分割成块。
strings 用于打印文件中可打印的字符串。
tac 与 cat 命令的功能相反,用于倒序地显示文件或连接文件。
tail 用于显示文件的结尾部分。
tee 用于从标准输入读取内容并写入到标准输出和文件。
tr 用于转换或删除字符。
uniq 用于报告或忽略重复的行。
wc 用于打印文件中的总行数、单词数或字节数。

grep

grep 'word' file    # 在 file 中查找并显示包含 word 的行
grep -i 'word' file # 忽略大小写
grep -R 'word' .    # 在当前目录及其子目录下的所有文件中查找并显示包含 word 的行
grep -c 'word' file # 搜索并显示 word 在 file 中出现的次数

事件提示符

!100  # 执行第 100 条命令
!-1   # 执行倒数第一条命令
!!    # !-1 的 alias

!abc  # 引用最近的以 abc 开头的命令。可以在运行一条命令之后忘记这个命令的参数时使用。
!?abc # 引用最近的包含 abc 的命令

# 间接取值
$ a="Hello"
$ b="a"
$ echo ${!b}
Hello

在 Shell 中,使用 ⬆️ 和 ⬇️ 也可以快速切换上一条、下一条命令。

命令连接符

符号 作用
; 没有任何逻辑关系的连接符。当多个命令用分号连接时,各命令之间的执行成功与否彼此没有任何影响,都会一条一条执行下去。
|| 逻辑或,当用此连接符连接多个命令时,前面的命令执行成功,则后面的命令不会执行。前面的命令执行失败,后面的命令才会执行。
&& 逻辑与,当用此连接符连接多个命令时,前面的命令执行成功,才会执行后面的命令,前面的命令执行失败,后面的命令不会执行。
| 管道符,当用此连接符连接多个命令时,前面命令执行的正确输出,会交给后面的命令继续处理。若前面的命令执行失败,则会报错,若后面的命令无法处理前面命令的输出,也会报错。

参见:Runoob: Shell 中的特殊字符

posted @ 2024-08-15 01:21  undefined443  阅读(210)  评论(0)    收藏  举报