shell手册

bash-handbook-zh-CN

目录

  • 前言
  • Shells与模式
    • 交互模式
    • 非交互模式
    • 返回值
  • 注释
  • 变量
    • 局部变量
    • 环境变量
    • 位置参数
  • Shell扩展
    • 大括号扩展
    • 命令置换
    • 算数扩展
    • 单引号和双引号
  • 数组
    • 数组声明
    • 数组扩展
    • 数组切片
    • 向数组中添加元素
    • 从数组中删除元素
  • 流,管道以及序列
    • 管道
    • 命令序列
  • 条件语句
    • 基元和组合表达式
    • 使用if
    • 使用case
  • 循环
    • for循环
    • while循环
    • until循环
    • select循环
    • 循环控制
  • 函数
  • Debugging
  • 后记
  • 其它资源
  • License

前言

如果你是一个程序员,时间的价值想必心中有数。持续优化工作流是你最重要的工作之一。

在通往高效和高生产力的路上,我们经常不得不做一些重复的劳动,比如:

  • 对屏幕截图,并把截图上传到服务器上
  • 处理各种各种的文本
  • 在不同格式之间转换文件
  • 格式化一个程序的输出

就让Bash来拯救我们吧。

Bash是一个Unix Shell,作为Bourne shell的free software替代品,由Brian Fox为GNU项目编写。它发布于1989年,在很长一段时间,Linux系统和macOS系统都把Bash作为默认的shell。

那么,我们学习这个有着30多年历史的东西意义何在呢?答案很简单:这是当今最强大、可移植性最好的,为所有基于Unix的系统编写高效率脚本的工具之一。这就是你需要学习bash的原因。

在本文中,我会用例子来描述在bash中最重要的思想。希望这篇概略性的文章对你有帮助。

Shells与模式

bash shell有交互和非交互两种模式。

交互模式

Ubuntu用户都知道,在Ubuntu中有7个虚拟终端。
桌面环境接管了第7个虚拟终端,于是按下Ctrl-Alt-F7,可以进入一个操作友好的图形用户界面。

即便如此,还是可以通过Ctrl-Alt-F1,来打开shell。打开后,熟悉的图形用户界面消失了,一个虚拟终端展现出来。

看到形如下面的东西,说明shell处于交互模式下:

user@host:~$

接着,便可以输入一系列Unix命令,比如lsgrepcdmkdirrm,来看它们的执行结果。

之所以把这种模式叫做交互式,是因为shell直接与用户交互。

直接使用虚拟终端其实并不是很方便。设想一下,当你想编辑一个文档,与此同时又想执行另一个命令,这种情况下,下面的虚拟终端模拟器可能更适合:

  • GNOME Terminal
  • Terminator
  • iTerm2
  • ConEmu

非交互模式

在非交互模式下,shell从文件或者管道中读取命令并执行。当shell解释器执行完文件中的最后一个命令,shell进程终止,并回到父进程。

可以使用下面的命令让shell以非交互模式运行:

sh /path/to/script.sh
bash /path/to/script.sh

上面的例子中,script.sh是一个包含shell解释器可以识别并执行的命令的普通文本文件,shbash是shell解释器程序。你可以使用任何喜欢的编辑器创建script.sh(vim,nano,Sublime Text, Atom等等)。

除此之外,你还可以通过chmod命令给文件添加可执行的权限,来直接执行脚本文件:

chmod +x /path/to/script.sh

这种方式要求脚本文件的第一行必须指明运行该脚本的程序,比如:

#!/bin/bash
echo "Hello, world!"

如果你更喜欢用sh,把#!/bin/bash改成#!/bin/sh即可。#!被称作shebang。之后,就能这样来运行脚本了:

/path/to/script.sh

上面的例子中,我们使用了一个很有用的命令echo来输出字符串到屏幕上。

我们还可以这样来使用shebang:

#!/usr/bin/env bash
echo "Hello, world!"

这样做的好处是,系统会自动在PATH环境变量中查找你指定的程序(本例中的bash)。相比第一种写法,你应该尽量用这种写法,因为程序的路径是不确定的。这样写还有一个好处,操作系统的PATH变量有可能被配置为指向程序的另一个版本。比如,安装完新版本的bash,我们可能将其路径添加到PATH中,来“隐藏”老版本。如果直接用#!/bin/bash,那么系统会选择老版本的bash来执行脚本,如果用#!/usr/bin/env bash,则会使用新版本。

返回值

每个命令都有一个返回值返回状态或者退出状态)。命令执行成功的返回值总是0(零值),执行失败的命令,返回一个非0值(错误码)。错误码必须是一个1到255之间的整数。

在编写脚本时,另一个很有用的命令是exit。这个命令被用来终止当前的执行,并把返回值交给shell。当exit不带任何参数时,它会终止当前脚本的执行并返回在它之前最后一个执行的命令的返回值。

一个程序运行结束后,shell将其返回值赋值给$?环境变量。因此$?变量通常被用来检测一个脚本执行成功与否。

与使用exit来结束一个脚本的执行类似,我们可以使用return命令来结束一个函数的执行并将返回值返回给调用者。当然,也可以在函数内部用exit,这 不但 会中止函数的继续执行,而且 会终止整个程序的执行。

注释

脚本中可以包含 注释。注释是特殊的语句,会被shell解释器忽略。它们以#开头,到行尾结束。

一个例子:

#!/bin/bash
# This script will print your username.
whoami

Tip: 用注释来说明你的脚本是干什么的,以及 为什么 这样写。

变量

跟许多程序设计语言一样,你可以在bash中创建变量。

Bash中没有数据类型。变量只能包含数字或者由一个或多个字符组成的字符串。你可以创建三种变量:局部变量,环境变量以及作为 位置参数 的变量。

局部变量

局部变量 是仅在某个脚本内部有效的变量。它们不能被其他的程序和脚本访问。

局部变量可以用=声明(作为一种约定,变量名、=、变量的值之间 不应该 有空格),其值可以用$访问到。举个例子:

username="denysdovhan"  # 声明变量
echo $username          # 输出变量的值
unset username          # 删除变量

我们可以用local关键字声明属于某个函数的局部变量。这样声明的变量会在函数结束时消失。

local local_var="I'm a local value"

环境变量

环境变量 是对当前shell会话内所有的程序或脚本都可见的变量。创建它们跟创建局部变量类似,但使用的是export关键字。

export GLOBAL_VAR="I'm a global variable"

bash中有 非常多 的环境变量。你会非常频繁地遇到它们,这里有一张速查表,记录了在实践中最常见的环境变量。

VariableDescription
$HOME当前用户的用户目录
$PATH用分号分隔的目录列表,shell会到这些目录中查找命令
$PWD当前工作目录
$RANDOM0到32767之间的整数
$UID数值类型,当前用户的用户ID
$PS1主要系统输入提示符
$PS2次要系统输入提示符

这里有一张更全面的Bash环境变量列表。

位置参数

位置参数 是在调用一个函数并传给它参数时创建的变量。下表列出了在函数中,位置参数变量和一些其它的特殊变量以及它们的意义。

ParameterDescription
$0脚本名称
$1 … $9第1个到第9个参数列表
${10} … ${N}第10个到N个参数列表
$* or $@除了$0外的所有位置参数
$#不包括$0在内的位置参数的个数
$FUNCNAME函数名称(仅在函数内部有值)

在下面的例子中,位置参数为:$0='./script.sh'$1='foo'$2='bar'

./script.sh foo bar

变量可以有 默认 值。我们可以用如下语法来指定默认值:

 # 如果变量为空,赋给他们默认值
: ${VAR:='default'}
: ${$1:='first'}
# 或者
FOO=${FOO:-'default'}

Shell扩展

扩展 发生在一行命令被分成一个个的 记号(tokens) 之后。换言之,扩展是一种执行数学运算的机制,还可以用来保存命令的执行结果,等等。

感兴趣的话可以阅读关于shell扩展的更多细节。

大括号扩展

大括号扩展让生成任意的字符串成为可能。它跟 文件名扩展 很类似,举个例子:

echo beg{i,a,u}n # begin began begun

大括号扩展还可以用来创建一个可被循环迭代的区间。

echo {0..5} # 0 1 2 3 4 5
echo {00..8..2} # 00 02 04 06 08

命令置换

命令置换允许我们对一个命令求值,并将其值置换到另一个命令或者变量赋值表达式中。当一个命令被````````或$()包围时,命令置换将会执行。举个例子:

now=`date +%T`
# or
now=$(date +%T)echo $now # 19:08:26

算数扩展

在bash中,执行算数运算是非常方便的。算数表达式必须包在$(( ))中。算数扩展的格式为:

result=$(( ((10 + 5*3) - 7) / 2 ))
echo $result # 9

在算数表达式中,使用变量无需带上$前缀:

x=4
y=7
echo $(( x + y ))     # 11
echo $(( ++x + y++ )) # 12
echo $(( x + y ))     # 13

单引号和双引号

单引号和双引号之间有很重要的区别。在双引号中,变量引用或者命令置换是会被展开的。在单引号中是不会的。举个例子:

echo "Your home: $HOME" # Your home: /Users/<username>
echo 'Your home: $HOME' # Your home: $HOME

当局部变量和环境变量包含空格时,它们在引号中的扩展要格外注意。随便举个例子,假如我们用echo来输出用户的输入:

INPUT="A string  with   strange    whitespace."
echo $INPUT   # A string with strange whitespace.
echo "$INPUT" # A string  with   strange    whitespace.

调用第一个echo时给了它5个单独的参数 —— I N P U T 被分成了单独的词, ‘ e c h o ‘ 在每个词之间打印了一个空格。第二种情况,调用 ‘ e c h o ‘ 时只给了它一个参数(整个 INPUT被分成了单独的词,`echo`在每个词之间打印了一个空格。第二种情况,调用`echo`时只给了它一个参数(整个 INPUT被分成了单独的词,echo在每个词之间打印了一个空格。第二种情况,调用echo时只给了它一个参数(整个INPUT的值,包括其中的空格)。

来看一个更严肃的例子:

FILE="Favorite Things.txt"
cat $FILE   # 尝试输出两个文件: `Favorite` 和 `Things.txt`
cat "$FILE" # 输出一个文件: `Favorite Things.txt`

尽管这个问题可以通过把FILE重命名成Favorite-Things.txt来解决,但是,假如这个值来自某个环境变量,来自一个位置参数,或者来自其它命令(find, cat, 等等)呢。因此,如果输入 可能 包含空格,务必要用引号把表达式包起来。

数组

跟其它程序设计语言一样,bash中的数组变量给了你引用多个值的能力。在bash中,数组下标也是从0开始,也就是说,第一个元素的下标是0。

跟数组打交道时,要注意一个特殊的环境变量IFSIFS,全称 Input Field Separator,保存了数组中元素的分隔符。它的默认值是一个空格IFS=' '

数组声明

在bash中,可以通过简单地给数组变量的某个下标赋值来创建一个数组:

fruits[0]=Apple
fruits[1]=Pear
fruits[2]=Plum

数组变量也可以通过复合赋值的方式来创建,比如:

fruits=(Apple Pear Plum)

数组扩展

单个数组元素的扩展跟普通变量的扩展类似:

echo ${fruits[1]} # Pear

整个数组可以通过把数字下标换成*@来扩展:

echo ${fruits[*]} # Apple Pear Plum
echo ${fruits[@]} # Apple Pear Plum

上面两行有很重要(也很微妙)的区别,假设某数组元素中包含空格:

fruits[0]=Apple
fruits[1]="Desert fig"
fruits[2]=Plum

为了将数组中每个元素单独一行输出,我们用内建的printf命令:

printf "+ %s\n" ${fruits[*]}
# + Apple
# + Desert
# + fig
# + Plum

为什么Desertfig各占了一行?尝试用引号包起来:

printf "+ %s\n" "${fruits[*]}"
# + Apple Desert fig Plum

现在所有的元素都跑去了一行 —— 这不是我们想要的!为了解决这个痛点,${fruits[@]}闪亮登场:

printf "+ %s\n" "${fruits[@]}"
# + Apple
# + Desert fig
# + Plum

在引号内,${fruits[@]}将数组中的每个元素扩展为一个单独的参数;数组元素中的空格得以保留。

数组切片

除此之外,可以通过 切片 运算符来取出数组中的某一片元素:

echo ${fruits[@]:0:2} # Apple Desert fig

在上面的例子中,${fruits[@]}扩展为整个数组,:0:2取出了数组中从0开始,长度为2的元素。

向数组中添加元素

向数组中添加元素也非常简单。复合赋值在这里显得格外有用。我们可以这样做:

fruits=(Orange "${fruits[@]}" Banana Cherry)
echo ${fruits[@]} # Orange Apple Desert fig Plum Banana Cherry

上面的例子中,${fruits[@]}扩展为整个数组,并被置换到复合赋值语句中,接着,对数组fruits的赋值覆盖了它原来的值。

从数组中删除元素

unset命令来从数组中删除一个元素:

unset fruits[0]
echo ${fruits[@]} # Apple Desert fig Plum Banana Cherry

流,管道以及序列

Bash有很强大的工具来处理程序之间的协同工作。使用流,我们能将一个程序的输出发送到另一个程序或文件,因此,我们能方便地记录日志或做一些其它我们想做的事。

管道给了我们创建传送带的机会,控制程序的执行成为可能。

学习如何使用这些强大的、高级的工具是非常非常重要的。

Bash接收输入,并以字符序列或 字符流 的形式产生输出。这些流能被重定向到文件或另一个流中。

有三个文件描述符:

代码描述符描述
0stdin标准输入
1stdout标准输出
2stderr标准错误输出

重定向让我们可以控制一个命令的输入来自哪里,输出结果到什么地方。这些运算符在控制流的重定向时会被用到:

OperatorDescription
>重定向输出
&>重定向输出和错误输出
&>>以附加的形式重定向输出和错误输出
<重定向输入
<<Here文档 语法
<<<Here字符串

以下是一些使用重定向的例子:

# ls的结果将会被写到list.txt中
ls -l > list.txt# 将输出附加到list.txt中
ls -a >> list.txt# 所有的错误信息会被写到errors.txt中
grep da * 2> errors.txt# 从errors.txt中读取输入
less < errors.txt

管道

我们不仅能将流重定向到文件中,还能重定向到其它程序中。管道 允许我们把一个程序的输出当做另一个程序的输入。

在下面的例子中,command1把它的输出发送给了command2,然后输出被传递到command3

command1 | command2 | command3

这样的结构被称作 管道

在实际操作中,这可以用来在多个程序间依次处理数据。在下面的例子中,ls -l的输出被发送给了grep,来打印出扩展名是.md的文件,它的输出最终发送给了less

ls -l | grep .md$ | less

管道的返回值通常是管道中最后一个命令的返回值。shell会等到管道中所有的命令都结束后,才会返回一个值。如果你想让管道中任意一个命令失败后,管道就宣告失败,那么需要用下面的命令设置pipefail选项:

set -o pipefail

命令序列

命令序列是由;&&&或者||运算符分隔的一个或多个管道序列。

如果一个命令以&结尾,shell将会在一个子shell中异步执行这个命令。换句话说,这个命令将会在后台执行。

;分隔的命令将会依次执行:一个接着一个。shell会等待直到每个命令执行完。

# command2 会在 command1 之后执行
command1 ; command2# 等同于这种写法
command1
command2

&&||分隔的命令分别叫做 序列。

与序列 看起来是这样的:

# 当且仅当command1执行成功(返回0值)时,command2才会执行
command1 && command2

或序列 是下面这种形式:

# 当且仅当command1执行失败(返回错误码)时,command2才会执行
command1 || command2

序列的返回值是序列中最后一个执行的命令的返回值。

条件语句

跟其它程序设计语言一样,Bash中的条件语句让我们可以决定一个操作是否被执行。结果取决于一个包在[[ ]]里的表达式。

条件表达式可以包含&&||运算符,分别对应 。除此之外还有很多有用的表达式。

共有两个不同的条件表达式:ifcase

基元和组合表达式

[[ ]]sh中是[ ])包起来的表达式被称作 检测命令基元。这些表达式帮助我们检测一个条件的结果。在下面的表里,为了兼容sh,我们用的是[ ]。这里可以找到有关bash中单双中括号区别的答案。

跟文件系统相关:

基元含义
[ -e FILE ]如果FILE存在 (exists),为真
[ -f FILE ]如果FILE存在且为一个普通文件(file),为真
[ -d FILE ]如果FILE存在且为一个目录(directory),为真
[ -s FILE ]如果FILE存在且非空(size 大于0),为真
[ -r FILE ]如果FILE存在且有读权限(readable),为真
[ -w FILE ]如果FILE存在且有写权限(writable),为真
[ -x FILE ]如果FILE存在且有可执行权限(executable),为真
[ -L FILE ]如果FILE存在且为一个符号链接(link),为真
[ FILE1 -nt FILE2 ]FILE1FILE2新(newer than)
[ FILE1 -ot FILE2 ]FILE1FILE2旧(older than)

跟字符串相关:

基元含义
[ -z STR ]STR为空(长度为0,zero)
[ -n STR ]STR非空(长度非0,non-zero)
[ STR1 == STR2 ]STR1STR2相等
[ STR1 != STR2 ]STR1STR2不等

算数二元运算符:

基元含义
[ ARG1 -eq ARG2 ]ARG1ARG2相等(equal)
[ ARG1 -ne ARG2 ]ARG1ARG2不等(not equal)
[ ARG1 -lt ARG2 ]ARG1小于ARG2less than)
[ ARG1 -le ARG2 ]ARG1小于等于ARG2less than or equal)
[ ARG1 -gt ARG2 ]ARG1大于ARG2greater than)
[ ARG1 -ge ARG2 ]ARG1大于等于ARG2greater than or equal)

条件语句可以跟 组合表达式 配合使用:

OperationEffect
[ ! EXPR ]如果EXPR为假,为真
[ (EXPR) ]返回EXPR的值
[ EXPR1 -a EXPR2 ]逻辑 , 如果EXPR1和(and)EXPR2都为真,为真
[ EXPR1 -o EXPR2 ]逻辑 , 如果EXPR1或(or)EXPR2为真,为真

当然,还有很多有用的基元,在Bash的man页面能很容易找到它们。

使用if

if在使用上跟其它语言相同。如果中括号里的表达式为真,那么thenfi之间的代码会被执行。fi标志着条件代码块的结束。

# 写成一行
if [[ 1 -eq 1 ]]; then echo "true"; fi# 写成多行
if [[ 1 -eq 1 ]]; thenecho "true"
fi

同样,我们可以使用if..else语句,例如:

# 写成一行
if [[ 2 -ne 1 ]]; then echo "true"; else echo "false"; fi# 写成多行
if [[ 2 -ne 1 ]]; thenecho "true"
elseecho "false"
fi

有些时候,if..else不能满足我们的要求。别忘了if..elif..else,使用起来也很方便。

看下面的例子:

if [[ `uname` == "Adam" ]]; thenecho "Do not eat an apple!"
elif [[ `uname` == "Eva" ]]; thenecho "Do not take an apple!"
elseecho "Apples are delicious!"
fi

使用case

如果你需要面对很多情况,分别要采取不同的措施,那么使用case会比嵌套的if更有用。使用case来解决复杂的条件判断,看起来像下面这样:

case "$extension" in"jpg"|"jpeg")echo "It's image with jpeg extension.";;"png")echo "It's image with png extension.";;"gif")echo "Oh, it's a giphy!";;*)echo "Woops! It's not image!";;
esac

每种情况都是匹配了某个模式的表达式。|用来分割多个模式,)用来结束一个模式序列。第一个匹配上的模式对应的命令将会被执行。*代表任何不匹配以上给定模式的模式。命令块儿之间要用;;分隔。

循环

循环其实不足为奇。跟其它程序设计语言一样,bash中的循环也是只要控制条件为真就一直迭代执行的代码块。

Bash中有四种循环:forwhileuntilselect

for循环

for与它在C语言中的姊妹非常像。看起来是这样:

for arg in elem1 elem2 ... elemN
do# 语句
done

在每次循环的过程中,arg依次被赋值为从elem1elemN。这些值还可以是通配符或者大括号扩展。

当然,我们还可以把for循环写在一行,但这要求do之前要有一个分号,就像下面这样:

for i in {1..5}; do echo $i; done

还有,如果你觉得for..in..do对你来说有点奇怪,那么你也可以像C语言那样使用for,比如:

for (( i = 0; i < 10; i++ )); doecho $i
done

当我们想对一个目录下的所有文件做同样的操作时,for就很方便了。举个例子,如果我们想把所有的.bash文件移动到script文件夹中,并给它们可执行权限,我们的脚本可以这样写:

#!/bin/bashfor FILE in $HOME/*.bash; domv "$FILE" "${HOME}/scripts"chmod +x "${HOME}/scripts/${FILE}"
done

while循环

while循环检测一个条件,只要这个条件为 ,就执行一段命令。被检测的条件跟if..then中使用的基元并无二异。因此一个while循环看起来会是这样:

while [[ condition ]]
do# 语句
done

for循环一样,如果我们把do和被检测的条件写到一行,那么必须要在do之前加一个分号。

比如下面这个例子:

#!/bin/bash# 0到9之间每个数的平方
x=0
while [[ $x -lt 10 ]]; do # x小于10echo $(( x * x ))x=$(( x + 1 )) # x加1
done

until循环

until循环跟while循环正好相反。它跟while一样也需要检测一个测试条件,但不同的是,只要该条件为 就一直执行循环:

until [[ condition ]]; do# 语句
done

select循环

select循环帮助我们组织一个用户菜单。它的语法几乎跟for循环一致:

select answer in elem1 elem2 ... elemN
do# 语句
done

select会打印elem1..elemN以及它们的序列号到屏幕上,之后会提示用户输入。通常看到的是$?PS3变量)。用户的选择结果会被保存到answer中。如果answer是一个在1..N之间的数字,那么语句会被执行,紧接着会进行下一次迭代 —— 如果不想这样的话我们可以使用break语句。

一个可能的实例可能会是这样:

#!/bin/bashPS3="Choose the package manager: "
select ITEM in bower npm gem pip
doecho -n "Enter the package name: " && read PACKAGEcase $ITEM inbower) bower install $PACKAGE ;;npm)   npm   install $PACKAGE ;;gem)   gem   install $PACKAGE ;;pip)   pip   install $PACKAGE ;;esacbreak # 避免无限循环
done

这个例子,先询问用户他想使用什么包管理器。接着,又询问了想安装什么包,最后执行安装操作。

运行这个脚本,会得到如下输出:

$ ./my_script
1) bower
2) npm
3) gem
4) pip
Choose the package manager: 2
Enter the package name: bash-handbook
<installing bash-handbook>

循环控制

我们会遇到想提前结束一个循环或跳过某次循环执行的情况。这些可以使用shell内建的breakcontinue语句来实现。它们可以在任何循环中使用。

break语句用来提前结束当前循环。我们之前已经见过它了。

continue语句用来跳过某次迭代。我们可以这么来用它:

for (( i = 0; i < 10; i++ )); doif [[ $(( i % 2 )) -eq 0 ]]; then continue; fiecho $i
done

运行上面的例子,会打印出所有0到9之间的奇数。

函数

在脚本中,我们可以定义并调用函数。跟其它程序设计语言类似,函数是一个代码块,但有所不同。

bash中,函数是一个命令序列,这个命令序列组织在某个名字下面,即 函数名 。调用函数跟其它语言一样,写下函数名字,函数就会被 调用

我们可以这样声明函数:

my_func () {# 语句
}my_func # 调用 my_func

我们必须在调用前声明函数。

函数可以接收参数并返回结果 —— 返回值。参数,在函数内部,跟非交互式下的脚本参数处理方式相同 —— 使用位置参数。返回值可以使用return命令 返回

下面这个函数接收一个名字参数,返回0,表示成功执行。

# 带参数的函数
greeting () {if [[ -n $1 ]]; thenecho "Hello, $1!"elseecho "Hello, unknown!"fireturn 0
}greeting Denys  # Hello, Denys!
greeting        # Hello, unknown!

我们之前已经介绍过返回值。不带任何参数的return会返回最后一个执行的命令的返回值。上面的例子,return 0会返回一个成功表示执行的值,0

Debugging

shell提供了用于debugging脚本的工具。如果我们想以debug模式运行某脚本,可以在其shebang中使用一个特殊的选项:

#!/bin/bash options

options是一些可以改变shell行为的选项。下表是一些可能对你有用的选项:

ShortNameDescription
-fnoglob禁止文件名展开(globbing)
-iinteractive让脚本以 交互 模式运行
-nnoexec读取命令,但不执行(语法检查)
-t执行完第一条命令后退出
-vverbose在执行每条命令前,向stderr输出该命令
-xxtrace在执行每条命令前,向stderr输出该命令以及该命令的扩展参数

举个例子,如果我们在脚本中指定了-x例如:

#!/bin/bash -xfor (( i = 0; i < 3; i++ )); doecho $i
done

这会向stdout打印出变量的值和一些其它有用的信息:

$ ./my_script
+ (( i = 0 ))
+ (( i < 3 ))
+ echo 0
0
+ (( i++  ))
+ (( i < 3 ))
+ echo 1
1
+ (( i++  ))
+ (( i < 3 ))
+ echo 2
2
+ (( i++  ))
+ (( i < 3 ))

有时我们需要debug脚本的一部分。这种情况下,使用set命令会很方便。这个命令可以启用或禁用选项。使用-启用选项,+禁用选项:

#!/bin/bashecho "xtrace is turned off"
set -x
echo "xtrace is enabled"
set +x
echo "xtrace is turned off again"

#](./translator-notes.md)

我希望这本小小的册子能很有趣且很有帮助。老实说,我写这本小册子是为了自己不会忘了bash的基础知识。我尽量让文字简明达意,希望你们会喜欢。

这本小册子讲述了我自己的Bash经验。它并非全面综合,因此如果你想了解更多,请运行man bash,从那里开始。

非常欢迎您的贡献,任何指正和问题我都非常感激。这些都可以通过创建一个issue来进行。

感谢您的阅读!

想了解更多?

下面是一些其它有关Bash的资料:

  • Bash的man页面。在Bash可以运行的众多环境中,通过运行man bash可以借助帮助系统man来显示Bash的帮助信息。有关man命令的更多信息,请看托管在The Linux Information Project上的网页"The man Command"。
  • “Bourne-Again SHell manual”,有很多可选的格式,包括HTML,Info,Tex,PDF,以及Textinfo。托管在https://www.gnu.org/上。截止到2016/01,它基于的是Bash的4.3版本,最后更新日期是2015/02/02。

其它资源

  • awesome-bash,是一个组织有序的有关Bash脚本以及相关资源的列表
  • awesome-shell,另一个组织有序的shell资源列表
  • bash-it,为你日常使用,开发以及维护shell脚本和自定义命令提供了一个可靠的框架
  • dotfiles.github.io,上面有bash和其它shell的各种dotfiles集合以及shell框架的链接
  • learnyoubash,帮助你编写你的第一个bash脚本
  • shellcheck,一个shell脚本的静态分析工具,既可以在网页www.shellcheck.net上使用它,又可以在命令行中使用,安装教程在koalaman/shellcheck的github仓库页面上

最后,Stack Overflow上bash标签下有很多你可以学习的问题,当你遇到问题时,也是一个提问的好地方。

License

![CC 4.0][cc-image]

© Denys Dovhan

![CC 4.0][cc-image]

© Shuai Liu For Chinese translation

[cc-image]: https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=https%3A%2F%2Fimg.shields.io%2Fbadge%2FLicense-CC%2520BY%25204.0-lightgrey.svg%3Fstyle%3Dflat-square&pos_id=img-jmmVrV3u-1726016428996)

译者手记

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/52497.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PCIe NVMe SSD 上电初始化全流程

1. 在FPGA中对PCIe IP核中的各种寄存器进行最原始的配置&#xff0c;比如Vendor ID, Device ID, Revision ID, Class Vaule等等 2. HOST首先将PCIe的Bar全写入1&#xff0c;来获取NVMe寄存器的大小&#xff0c;并在内核空间中开辟一块内存&#xff08;不是真的通过kalloc去开辟…

Vuex:深入理解所涉及的几个问题

你好&#xff0c;我是沐爸&#xff0c;欢迎点赞、收藏、评论和关注。 一、Vuex 是什么&#xff1f; Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 二、Vu…

别找了!包含gpt在内的国内可以使用的Ai网站都在这了【最新可用】

在当今人工智能迅速发展的时代&#xff0c;智能创作与对话平台为用户提供了多样化的功能支持。以下是一些国内代表性的GPT平台&#xff0c;涵盖了从个人到企业的广泛需求&#xff0c;您可以根据自己的需求灵活选择。我们还为您整理了这些平台的链接&#xff0c;方便直接体验。&…

Redis学习Day3——项目工程开发

扩展阅读推荐&#xff1a; 黑马程序员Redis入门到实战教程_哔哩哔哩_bilibili 使用git命令行将本地仓库代码上传到gitee/github远程仓库-CSDN博客 一、项目介绍及其初始化 学习Redis的过程&#xff0c;我们还将遇到各种实际问题&#xff0c;例如缓存击穿、雪崩、热Key等问题&…

Ubuntu20.04+ros-noetic配置Cartographer

一、概述 因为要配置激光SLAM&#xff0c;Cartographer属于激光雷达SLAM 中比较经典的一款&#xff0c;在学习之前先将其在Ubuntu20.04首先配置出来并成功运行demo。 二、具体操作 &#xff08;一&#xff09;概述 使用平台是Windows的wsl2上的Ubuntu20.04子系统&#xff0c;…

C语言中的磁盘映射与共享内存详解

文章目录 C语言中的磁盘映射与共享内存1. 磁盘映射&#xff08;Memory Mapping&#xff09;1.1 磁盘映射的深入概念1.2 mmap函数的详细参数解析1.3 磁盘映射的高级应用场景1.3.1 大文件处理1.3.2 内存共享1.3.3 文件与内存同步1.3.4 内存映射数据库 1.4 完整的磁盘映射代码示例…

np.ndarray和np.array区别;MXNet的 mx.array 类型是什么;NDArray优化了什么:并行计算优化

目录 np.ndarray和np.array区别 np.ndarray np.array 举例说明 MXNet的 mx.array 类型是什么 NDArray优化了什么 1. 异步计算和内存优化 2. 高效的数学和线性代数运算 3. 稀疏数据支持 4. 自动化求导 举例说明 np.ndarray和np.array区别 在NumPy库中,np.ndarray和n…

如何看待IBM中国研发部裁员?

如何看待IBM中国研发部裁员&#xff1f;近日&#xff0c;IBM中国宣布撤出在华两大研发中心&#xff0c;引发了IT行业对于跨国公司在华研发战略的广泛讨论。这一决定不仅影响了众多IT从业者的职业发展&#xff0c;也让人思考全球化背景下中国IT产业的竞争力和未来发展方向。面对…

Java+vue的医药进出口交易系统(源码+数据库+文档)

外贸系统|医药进出口交易系统 目录 基于Javavue的服装定制系统 一、前言 二、系统设计 三、系统功能设计 仓储部门功能模块 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设…

2024.09.04 校招 实习 内推 面经

&#x1f6f0;️ &#xff1a;neituijunsir 交* 流*裙 &#xff0c;内推/实习/校招汇总表格 1、校招 | 海康威视2025届校园招聘正式启动&#xff08;内推&#xff09; 校招 | 海康威视2025届校园招聘正式启动&#xff08;内推&#xff09; 2、校招 | 沃飞长空2025届全球校…

中国书法——孙溟㠭浅析碑帖《三希堂法帖》

孙溟㠭浅析碑帖《三希堂法帖》 全称是《三希堂石渠宝笈法帖》&#xff0c;是中国清代宫廷刻帖&#xff0c;一共三十二册。 清朝高宗弘历收藏了晋王羲之《快雪时晴帖》&#xff0c;王献之的《中秋帖》&#xff0c;王珣的《伯远帖》三种王氏原墨迹。故而把所藏法书之所…

农产品管理与推荐系统Python+Django网页界面+计算机毕设项目+推荐算法

一、介绍 农产品管理与推荐系统。本系统使用Python作为主要开发语言&#xff0c;前端使用HTML&#xff0c;CSS&#xff0c;BootStrap等技术和框架搭建前端界面&#xff0c;后端使用Django框架处理应用请求&#xff0c;使用Ajax等技术实现前后端的数据通信。实现了一个综合性的…

2024年9月10日嵌入式学习

今日主要学习了缓冲帧。 Framebuffer&#xff08;帧缓冲&#xff09;是Linux系统为显示设备提供的一套应用程序接口&#xff0c;它将显存抽象为一种设备&#xff0c;允许上层应用程序在图形模式下直接进行显示缓冲区的读写操作。 原理&#xff1a;通过内存映射技术向显存空间…

MM-PhyQA——一个专门处理高中物理选择题的 LLM 聊天机器人

概述 论文地址&#xff1a;https://arxiv.org/abs/2404.12926 人工智能的发展正在改变我们的学习方式。特别是使用大规模语言模型&#xff08;LLM&#xff09;的聊天机器人&#xff0c;通过提供个性化指导和即时反馈&#xff0c;极大地拓展了教育的可能性。 然而&#xff0c…

带你深入了解C语言指针(一)

目录 前言 一、内存和地址 1. 内存 2. 究竟该如何理解编址 二、指针变量和地址 1. 取地址操作符&#xff08;&&#xff09; 2. 指针变量和解引用操作符&#xff08;*&#xff09; 2.1 指针变量 2.2 如何拆解指针类型 2.3 解引⽤操作符 3. 指针变量的大小 三、指…

JavaScript更改属性名称+增加字段+排序

JavaScript更改属性名称增加字段排序 背景 客户提供的接口里包含了一堆数据&#xff0c;其中分为多个模块&#xff0c;需要进行拆分&#xff0c;其中涉及到名称更改、字段增加、排序。处理过程 -需要的数据&#xff1a; data: {"四年级": [{ "class": &q…

LeetCode题练习与总结:矩形面积--223

一、题目描述 给你 二维 平面上两个 由直线构成且边与坐标轴平行/垂直 的矩形&#xff0c;请你计算并返回两个矩形覆盖的总面积。 每个矩形由其 左下 顶点和 右上 顶点坐标表示&#xff1a; 第一个矩形由其左下顶点 (ax1, ay1) 和右上顶点 (ax2, ay2) 定义。第二个矩形由其左…

jina-embeddings 的使用教程,怎么用它做embeddings和rerank的操作呢?

Jina-embeddings 使用教程 Jina-embeddings 是一个强大的工具&#xff0c;可以用来生成文本的嵌入向量&#xff08;embeddings&#xff09;&#xff0c;这些向量可用于相似度搜索、分类、重排序&#xff08;reranking&#xff09;等任务。在这个教程中&#xff0c;我将展示如何…

配置 MinGW 以及使用 g++ 编译 C++ 程序

如何在 Windows 上安装和配置 MinGW 以及使用 g 编译 C 程序 (C语言&#xff08;gcc&#xff09;类似 ) 在Windows环境下&#xff0c;使用C进行编程需要一个编译器&#xff0c;而MinGW (Minimalist GNU for Windows) 是一个常用的C/C编译器工具集。对于编程新手来说&#xff0c…

SOMEIP_ETS_101: SD_ClientServiceActivate_send_StopOfferService

测试目的&#xff1a; 验证当DUT在客户端模式下开始发送FindService消息时&#xff0c;测试器发送StopOfferService后&#xff0c;DUT能够理解其正在寻找的服务和实例ID不再可用&#xff0c;并停止为此服务和实例ID发送FindService消息。同时&#xff0c;DUT仍然可以发送Find-…