惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

C
Cisco Blogs
Schneier on Security
Schneier on Security
T
Tor Project blog
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
T
Tenable Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
T
Threat Research - Cisco Blogs
C
CERT Recently Published Vulnerability Notes
Security Latest
Security Latest
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
NISL@THU
NISL@THU
L
Lohrmann on Cybersecurity
Scott Helme
Scott Helme
Webroot Blog
Webroot Blog
Project Zero
Project Zero
Google Online Security Blog
Google Online Security Blog
The Last Watchdog
The Last Watchdog
Spread Privacy
Spread Privacy
Hacker News: Ask HN
Hacker News: Ask HN
PCI Perspectives
PCI Perspectives
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
W
WeLiveSecurity
Attack and Defense Labs
Attack and Defense Labs
D
Darknet – Hacking Tools, Hacker News & Cyber Security
N
News | PayPal Newsroom
Help Net Security
Help Net Security
The Hacker News
The Hacker News
H
Heimdal Security Blog
O
OpenAI News
S
Security @ Cisco Blogs
N
News and Events Feed by Topic
Cyberwarzone
Cyberwarzone
Simon Willison's Weblog
Simon Willison's Weblog
G
GRAHAM CLULEY
www.infosecurity-magazine.com
www.infosecurity-magazine.com
博客园 - 叶小钗
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
Hacker News - Newest:
Hacker News - Newest: "LLM"
T
Tailwind CSS Blog
大猫的无限游戏
大猫的无限游戏
A
Arctic Wolf
I
Intezer
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
S
Security Affairs
P
Proofpoint News Feed
S
Secure Thoughts
腾讯CDC
Google DeepMind News
Google DeepMind News
量子位
罗磊的独立博客

Razeen`s Blog

Let's Encrypt 推出 Gen Y 根证书架构:揭示 Web PKI 的五大未来趋势 通过 Wi-Fi 自动备份你的 iPhone 到 Nas 管理培训感悟:从技术视角看管理之路 UPS 一拖多:保护你的 NAS 和 Linux 服务器 从影音中心到 AI 助手:我的50款 Homelab 服务清单 从SSL证书有效期将缩短到47天聊开去 部署一个自己的 Running Page 谈谈特斯拉 Model Y 用车一年的感受和费用分析 弃用 Disqus 评论,使用自建 Waline 使用 Prometheus 和 Grafana 搭建你的证书监控面板 服务器迁移记:一次磁盘换板引发的Linux分区与挂载的学习 改善信息来源, 利用 RSS 高效获取资讯 (RSShub + Reeder5 + WeWe RSS) 多种 Docker 镜像拉取解决方案与实践 App分享 | AppCleaner - Mac上的卸载神器 Azure OpenAI API 申请和使用 如何开通 OneKey 虚拟信用卡,并充值消费 如何拥有一个可长期在国内使用的国外手机号码 分享开通 ChatGPT Plus 过程 如何开通 Depay 虚拟信用卡,并充值消费 如何开通欧易Web3钱包, 交易入账 如何5分钟内1块钱注册 ChatGPT 如何注册美区的 Apple ID (2023年/无需科学上网) NAS折腾记(11): NASTool3.0体验和降级 NAS折腾记(10): Docker版本的NASTool配置 NAS折腾记(9): NASTool与微信交互,微信发送消息远程下载电影【多图】 NAS折腾记(8):群晖安装 NASTool 实现影音半自动化【多图】 内网穿透(2):Tailscale 组网实现内网穿透,操作简单,无成本 内网穿透(1):总结了11中内网穿透的方式,总有一种适合你 NAS折腾记(7):从零开始设置(黑)群晖系统 NAS折腾记(6):黑群晖系统安装好后怎么洗白? NAS折腾记(5):群晖硬盘休眠设置与分析 NAS折腾记(4): 20分钟手把手带你完美安装 DS918+ 黑群晖7.1.1 NAS折腾记(3):NAS 装机 NAS折腾记(2):B360-ITX 双M.2 双2.5G网口 6 SATA主版 开箱 NAS折腾记(1):328元的4盘位Nas机箱开箱 Openwrt + Clash 全局科学上网 Newifi3 刷入 OpenWrt 固件 v21.02 利用 Markdown 画一些流程图、时序图、甘特图等 Homelab(8): 搭建自用 Gitlab 与 Docker 仓库 Nginx Tcp 转发保留客户端真实 IP (PROXY Protocol) Homelab (7):IPSec VPN(基于证书认证)客户端设置 Homelab (6): 基于自签发证书的 IPSec VPN 搭建 Homelab (5): DDNS 动态域名解析 Homelab (4): Linux 服务器基础环境准备 Homelab (3): 整体网络与基础硬件介绍 Go学习笔记(十)老项目迁移 go module 大型灾难记录 Github Actions 初体验之自动化部署 Hexo 博客 记一次 Nginx DNS 缓存导致转发问题 Homelab (2): 电信悦me网关修改桥接模式,路由器拨号 Homelab (1):5分钟上手黑群晖 NAS Typora 自动上传图片到七牛云 Ubuntu 20.04 LTS 有线网卡驱动安装 折腾 Ubuntu 20.04 LTS 开发环境 Go学习笔记(九) 计时器的生命周期[译] 利用 git hook 规范你的代码与 commit message 规范 git commit message 与自动化版本控制 超详细 vim 配置 (with MacVim) Golang 中的 RESTful API 最佳实践 折腾服务器(开篇) 我的第一台个人服务器 Newifi3 实现低成本家庭级科学上网 Go学习笔记(八) | 使用 os/exec 执行命令 如何用 Go 调用 Windows API Mac OS 自动根据 WI-FI 名字改变网络位置 关于 Docker 清理 MIME Types 速查表 Go学习笔记(七) | 理解OAuth 2.0并实现一个客户端 我又又又把博客迁移了 Go学习笔记(六) | 使用swaggo自动生成Restful API文档 Go学习笔记(五) | 使用代码片段(snippets)提高编码效率 书单 - 2018 IPFS 初体验,利用 IPFS 托管你的静态网站 记一次 PostgreSQL LIKE 索引优化,联合字段 LIKE 查询优化。 Disqus 添加有趣的 Reactions 的功能 TLS 1.3 详解 (RFC 8446解读) gRPC在Go中的使用(三)gRPC实现TLS加密通信与流模式 gRPC在Go中的使用(二)gRPC实现简单通讯 gRPC在Go中的使用(一)Protocol Buffers语法与相关使用 CentOS 安装 tshark 抓包工具 Go学习笔记(四) | win上使用VSCode搭建Go开发环境 日常 Postgres 数据库点滴记录 简单了解 PKCS 规范 美食篇 | DIY戚风蛋糕(烤箱做蛋糕) Go学习笔记(三) | 怎么写Go基准测试(性能测试) TLS1.3正式更新,为Nginx添加TLS1.3的支持 一次诡异的数据库删除 GitHub Pages自定义域名开启HTTPS 证书透明度是什么?它是怎么工作的? Go学习笔记(二) | 我对 recover 的一点误解 搭建证书透明度(certificate-transparency)日志服务之从入门到放弃 修复远程登陆 Centos 时,出现 UTF-8 Warning HTTPS篇之SSL握手过程详解 Go学习笔记(一) | postgres与golang点点滴滴 AWS 命令行界面(aws-cli)从安装到快速上手 数字证书分类及怎么区分各类数字证书 常用 linux 命令小结(一)文件目录操作 云服务器搭建 hexo 博客,git hooks自动更新 SSH 免密登陆, SSH Config 配置 Golang CGO Mac 交叉编译 Windows 使用 goose 让数据库迁移更加轻松 开始使用Ghost
终极 Bash 脚本指南
2021-02-02 · via Razeen`s Blog

在开发的过程中,经常需要处理一些重复的工作,或者逻辑相当简单但耗时的功能,这时我们可能会考虑到用脚本来自动化完成这些工作。而 Bash 脚本是我们最容易接触到和上手的脚本语言。

这篇博客汇总一些常用的 Bash 语法,方便日后查阅学习。

hello world

不管写啥,上来先输出个hello world

#!/bin/bash

echo "hello world"

创建一个文件hello.sh 包含以上内容,同时赋予执行权限,然后执行,一个hello world 就好了。

# 添加执行权限
$ chmod +x hello.sh

$ ./hello.sh
hello world

解释器

我们看到这个hello.sh 脚本,第一行有个 #!/bin/bash 。 这个是用来指定该脚本在 UNIX/Linux 下执行时用到的解释器。

执行cat /etc/shells 我们可以看到自己的系统中都有哪些解释器。如我的:

$ cat /etc/shells
# /etc/shells: valid login shells
/bin/sh
/bin/bash
/usr/bin/bash
/bin/rbash
/usr/bin/rbash
/bin/dash
/usr/bin/dash
/bin/zsh
/usr/bin/zsh
/usr/bin/tmux

注释

# 来注释。

#!/bin/bash
# 这是注释
echo "hello world"

变量声明

Bash 中变量命名是大小写敏感的,很多喜欢全大写。当然你也可以使用 小写英文字母,数字和下划线,但不能以数字开头。 给变量赋值的时候 = 号前后不能有空格。

# 有效的
FIRSTLETTERS="ABC"
FIRST_THREE_LETTERS="ABC"
firstThreeLetters="ABC"
MY_SHELL="bash"
my_another_shell="my another shell"
My_Shell="My shell"
_myshell="My shell"

# 无效的
3LETTERS="ABC"
first-three-letters="ABC"
first@Thtree@Letters="ABC"
ABC = "ABC "
MY_SHELL = "bash"
My-SHELL="bash"
1MY_SHELL="My shell"

变量引用

当你要使用变量的时候,用 $ 来引用, 如果后面要接一些其他字符,可以用{} 括起来。

#!/bin/bash
WORLD="world world"
echo "hello $WORLD"  # hello world world
echo "hello ${WORLD}2" # hello world world2

在 Bash 中要注意 单引号 ' , 双引号 " ,反引号 ` 的区别。

单引号,双引号都能用来保留引号内的为文字值,其差别在于,双引号在遇到 $(参数替换) , 反引号 `(命令替换) 的时候有例外,单引号则剥夺其中所有字符的特殊含义。

而反引号的作用 和 $() 是差不多的。 在执行一条命令的时候,会先执行其中的命令,再把结果放到原命令中。

#!/bin/bash
var="music"
sports='sports'
echo "I like $var"   # I like music
echo "I like ${var}" # I like music
echo I like $var     # I like music
echo 'I like $var'   # I like $var
echo "I like \$var"  # I like $var
echo 'I like \$var'  # I like \$var
echo `bash -version` # GNU bash, version 5.0.17(1)-release (x86_64-pc-linux-gnu)...
echo 'bash -version' # bash -version

环境变量

Linux 的环境变量包含了存储在系统中的信息。我们可以在终端中找到一些环境变量。

你可以在脚本中引用这些环境变量。

#!/bin/bash
echo $SHELL, $USER, $HOME 

# /usr/bin/zsh, razeen, /home/razeen

这里 还有更多。

内部变量

Bash 的内部变量也不少,有时我们可能会用到,如 $BASHPID $IFS $PWD 等,更多看这里

将命令输出分配给变量

可以使用 $(command) 将命令输出存储在变量中。例如这是一个info.sh脚本内容:

#!/bin/bash

LIST=$(ls -l)
echo "File information: $LIST"

执行(别忘了给执行权限)

$ ./info.sh
File information: total 8
-rwxrwxr-x 1 razeen razeen 85 2月   5 07:35 hello.sh
-rwxrwxr-x 1 razeen razeen 58 2月   5 07:36 info.sh

下面的脚本会将时间和日期,用户名以及系统正常运行时间保存到日志文件中。

其中 > 是重定向之一,它将覆盖文件。使用 >> 可以将输出追加到文件。

#!/bin/bash
DATE=$(date -u) # UTC 时间#!/bin/bash
DATE=$(date -u) # UTC 时间
WHO=$(whoami) # 用户名
UPTIME=$(uptime) # 系统运行时间
echo "Today is $DATE. You are $WHO. Uptime info: $UPTIME" > logfile
WHO=$(whoami) # 用户名
UPTIME=$(uptime) # 系统运行时间
echo "Today is $DATE. You are $WHO. Uptime info: $UPTIME" > logfile

image-20210205074148588

内建命令

Shell 内建命令是可以直接在Shell中运行的命令。可以这么查看内建命令:

$ compgen -b | sort
-
.
:
[
alias
autoload
bg
bindkey
break
builtin
bye
cd

也可以用 type 查看命令的类型。

$ type cd
cd is a shell builtin

可以用 which 命令查看可执行文件的文件路径:

# which sort
/usr/bin/sort

可通过 man builtins 查看内建命令的详细描述。

测试

IF条件表达式

if 后面需要接者then

if [ condition-for-test ]
then
  command
  ...
fi

或者,

if [ condition-for-test ]; then
  command
  ...
fi

如:

#!/bin/bash
 
VAR=myvar
if [ $VAR = myvar ]; then
    echo "1: \$VAR is $VAR"   # 1: $VAR is myvar
fi
if [ "$VAR" = myvar ]; then
    echo "2: \$VAR is $VAR"   # 2: $VAR is myvar
fi
if [ $VAR = "myvar" ]; then
    echo "3: \$VAR is $VAR"   # 3: $VAR is myvar
fi
if [ "$VAR" = "myvar" ]; then
    echo "4: \$VAR is $VAR"   # 4: $VAR is myvar
fi

上面,我们在比较时,可以用双引号把变量引用起来。

但要注意单引号的使用。

#!/bin/bash
VAR=myvar
if [ '$VAR' = 'myvar' ]; then
    echo '5a: $VAR is $VAR'
else
    echo "5b: Not equal."
fibas
# Output:
# 5b: Not equal.

上面这个就把 ‘$VAR’ 当一个字符串了。

但如果变量是多个单词,我们就必须用到双引号了,如

#!/bin/bash

# 这样写就有问题
VAR1="my var"
if [ $VAR1 = "my var" ]; then
    echo "\$VAR1 is $VAR1"
fi
# Output
# error [: too many arguments

# 用双引号
if [ "$VAR1" = "my var" ]; then
    echo "\$VAR1 is $VAR1"
fi

总的来说,双引号可以一直加上。

空格问题

比较表达式中,如果=前后没有空格,那么整个表法式会被认为是一个单词,其判断结果为True.

#!/bin/bash
 
VAR2=2
#  由于被识别成一个单词, [] 里面为 true
if [ "$VAR2"=1 ]; then
    echo "$VAR2 is 1."
else
    echo "$VAR2 is not 1."
fi
# Output
# 2 is 1.


# 前后加上空格就好了
if [ "$VAR2" = 1 ]; then
    echo "$VAR2 is 1."
else
    echo "$VAR2 is not 1."
fi
# Output
# 2 is not 1.

另外需要注意的是, 在判断中,中括号 [ 和变量之间一定要有一个空格,= 或者 ==。 如果缺少了空格,你可能会到这类似这样的错误:unary operator expected’ or missing ]` 。

# 正确, 符号前后有空格
if [ $VAR2 = 1 ]; then
    echo "\$VAR2 is 1."
else
    echo "It's not 1."
fi
# Output
# 2 is 1.


# 错误, 符号前后无空格
if [$VAR2=1]; then
    echo "$VAR2 is 1."
else
    echo "It's not 1."
fi
# Output
# line 3: =1: command not found
# line 5: [=1]: command not found
# It's not 1.

文件测试表达式

对文件进行相关测试,判断的表达式如下:

表达式True
file1 -nt file2file1file2 新。
file1 -ot file2file1file2 老。
-d file文件file存在,且是一个文件夹。
-e file文件 file 存在。
-f file文件file存在,且为普通文件。
-L file文件file存在,且为符号连接。
-O file文件 flle 存在, 且由有效用户ID拥有。
-r file文件 flle 存在, 且是一个可读文件。
-s file文件 flle 存在, 且长度大于0。
-w file文件 flle 可写入。
-x file文件 flle 可写执行。

可以使用man test查看那详细的说明。

当表达式为True时,测试命令返回退出状态 0,而表达式为False时返回退出状态1。

#!/bin/bash
FILE="/etc/resolv.conf"
if [ -e "$FILE" ]; then
  if [ -f "$FILE" ]; then
      echo "$FILE is a file."
  fi
  if [ -d "$FILE" ]; then
      echo "$FILE is a directory."
  fi
  if [ -r "$FILE" ]; then
      echo "$FILE is readable."
  fi
fi

字符串比较表达式

表达式True
string1 = string2string1 == string2两字符相等
string1 != string2两个字符串不相等
string1 > string2string1 大于 string2.
string1 < string2string1 小于string2.
-n string字符串长度大于0
-z string字符串长度等于0
#!/bin/bash
STRING=""
if [ -z "$STRING" ]; then
  echo "There is no string." >&2 
  exit 1
fi

# Output
# There is no string.

其中>&2将错误信息定位到标准错误输出。

数字比较表达式

下面这些是用来比较数字的一些表达式。

[…]((…))True
[ “int1” -eq “int2” ](( “int1” == “int2” ))相等.
[ “int1” -nq “int2” ](( “int1” != “int2” ))不等.
[ “int1” -lt “int2” ](( “int1” < “int2” ))int2 大于 int1.
[ “int1” -le “int2” ](( “int1” <= “int2” ))int2 大于等于 int1.
[ “int1” -gt “int2” ](( “int1 > “int2” ))int1 大于 int2
[ “int1” -ge “int2” ](( “int1 >= “int2” ))int1 大于等于 int2

双括号 (())

数值的比较或者计算可以用((... ))

#!/bin/bash
a=3
b=4
c=3
if (("$a" < "$b")); then
    echo "$a is less than $b."
else
    echo "$a is not less than $b."
fi
if (("$a" != "$c")); then
    echo "$a is not equal to $c."
else
    echo "$a is equal to $c."
fi


# 计算
echo "$a + $b = $(($a + $b))"


# Output
# 3 is less than 4.
# 3 is equal to 3.
# 3 + 4 = 7

怎么使用 if/else 和 if/elif/else

其实上面已经展示了不少了,这里总结下if...elseif...elif...else 语句。

if/else 语句格式如下:

if [ condition-is-true ]
then
  command A
else
  command B
fi

# 或
if [ condition-is-true ]; then
  command A
else
  command B
fi

例如:

#!/bin/bash
MY_SHELL="csh"
if [ "$MY_SHELL" = "bash" ]
then
  echo "You are using the bash shell."
else
  echo "You are not using the bash shell."
fi

if/elif/else 语句格式如下:

if [ condition-is-true ]
then
  command A
elif [ condition-is-true ]
then
  command B
else
  command C
fi
# or
if [ condition-is-true ]; then
  command A
elif [ condition-is-true ]; then
  command B
else
  command C
fi

如:

#!/bin/bash
MY_SHELL="csh"
if [ "$MY_SHELL" = "bash" ]; then
  echo "You are using the bash shell."
elif [ "$MY_SHELL" = "csh" ]; then
  echo "You are using csh."
else
  echo "You are not using the bash shell."
fi

双中括号的使用[[]]

如用用于比较的变量不是单个单词,就需要[[]] , 或者用单中括号(这时需要加双引号)。 在平常的使用中,最好都使用[[]]

与单中括号相比,双中括号具有其他功能。 如,可以对其中正则使用逻辑&&||=〜

#!/bin/bash

VAR1="variable"
VAR2="variable 2"
if [[ (VAR1 == "variable")  ]]; then
    echo "They are the same."
else
    echo "Not the same."
fi

# 使用 &&
[[ ($VAR1 == variable) && (
$VAR2 == "variable 2") ]] && echo "They are the same again."
#!/bin/bash

digit=4
if [[ $digit =~ [0-9] ]]; then
    echo "$digit is a digit"
else
    echo "$digit isn't a digit"
fi
letter="abc"
if [[ $letter =~ [0-9] ]]; then
    echo "$letter is a digit"
else
    echo "$letter isn't a digit"
fi

# Output
# 4 is a digit
# abc isn't a digit

怎么使用 For 循环

for循环的使用如下:

for VARIABLE_NAME in ITEM_1 ITEM_N
do
  command A
done

例如:

#!/bin/bash
for COLOR in red green blue
do
  echo "COLOR: $COLOR"
done

# Output
# COLOR: red
# COLOR: green
# COLOR: blue

可以在其中使用变量,如下:

#!/bin/bash
COLORS="red green blue"
for COLOR in $COLORS
do
  echo "COLOR: $COLOR"
done

for 循环重命名文件

我们举个简单的例子,用for循环重命名当前目录下的jpg图片。

#!/bin/bash
IMGS=$(ls *jpg)
DATE=$(date +%F)
for IMG in $IMGS
do
  echo "Renaming ${IMG} to ${DATE}-${IMG}"
  mv ${IMG} ${DATE}-${IMG}
done

image-20210213170152011

怎么传参

执行脚本的时候,后面可以跟着很多参数,如:

$ scriptname param1 param2 param3

param1param3 称为可选参数, 可以在脚本中用 $0, $1, $2等,来引用这些参赛。例如:

#!/bin/bash
echo "'\$0' is $0"
echo "'\$1' is $1"
echo "'\$2' is $2"
echo "'\$3' is $3"

输出:

$ ./param.sh
'$0' is ./param.sh
'$1' is
'$2' is
'$3' is

$0 参数0返回的是当前执行文件的名字,包括路径。

可以用 $@ 接受所以的参数。

#!/bin/bash
for PARAM in $@
do
  echo "Param is: $PARAM"
done

Using this script:

$ ./params.sh a b c d e f
Param is: a
Param is: b
Param is: c
Param is: d
Param is: e
Param is: f

怎么接收用户输入

用户输入称为STDIN。可以将read命令与-p(提示)选项一起使用来读取用户输入,它将输出提示字符串。 -r 选项不允许反斜杠转义任何字符。

read -rp "PROMPT" VARIABLE

例如:

#!/bin/bash
read -rp "Enter your programming languages: " PROGRAMMES
echo "Your programming languages are: "
for PROGRAMME in $PROGRAMMES; do
    echo "$PROGRAMME "
done

运行:

$ ./read.sh
Enter your programming languages: go py
Your programming languages are:
go
py

用大括号来表示范围 {}

如下所示,我们可以用大括号来表所一个数字或字母的范围。

$ echo {0..3}
$ echo {a..d}

# output: 
# 0 1 2 3
# a b c d

你也可以在 for 循环中这么使用:

#!/bin/bash
for i in {0..9}; 
do 
  touch file_"$i".txt; 
done

This will create different file names with different modification times.

$ ls -al file_*
-rw-rw-r-- 1 razeen razeen 0 2月  14 09:54 file_0.txt
-rw-rw-r-- 1 razeen razeen 0 2月  14 09:54 file_1.txt
-rw-rw-r-- 1 razeen razeen 0 2月  14 09:54 file_2.txt
...

怎么使用While

当 While 后的表达式结果为 true时,执行循环内语句。

#!/bin/bash
i=1
while [ $i -le 5 ]; do
  echo $i
  ((i++))
done

Output:

退出码/返回码 是什么?

每个命令都返回退出状态,范围为0-255。 0代表成功,非0代表错误。 可以用来进行错误检查。

数值含义
0成功
2返回内置命令,从而提示错误
126命令找到了,但不是可执行的
127没有找到命令
128+N由于接收到信号N,命令退出

怎么检查退出码

$? 包含了上一条命令执行的返回码。

$ ls ./no/exist
ls: cannot access './no/exist': No such file or directory
$ echo "$?"
2

如,在if表达式中检查返回码:

#!/bin/bash

HOST="razeen.me"
ping -c 1 $HOST
RETURN_CODE=$?
if [ "$RETURN_CODE" -eq "0" ]; then
    echo "$HOST reachable."
else
    echo "$HOST unreachable."
fi

-c 1 参数表示发送一个可达包就停止发送。 然后我们检查一下ping执行的返回码。

输出:

$ ./ex.sh
PING razeen.me (47.108.161.7) 56(84) bytes of data.
64 bytes from 47.108.161.7 (47.108.161.7): icmp_seq=1 ttl=50 time=38.5 ms

--- razeen.me ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 38.541/38.541/38.541/0.000 ms
razeen.me reachable.

怎么连接多个命令

逻辑运算符和命令退出状态

执行命令后都有退出状态,我们可以使用 &&||去决定下一步。

退出命令

你可以使用 exit 来决定退出码:

exit 0
exit 1
exit 2
etc.

例如:

#!/bin/bash
HOST="razeen.me"
ping -c 1 $HOST  
if [ "$?" -ne "0" ]
then
  echo "$HOST unreachable."
  exit 1
fi
exit 0

我们可以将该脚本通过&&与其他脚本/命令连接。

$ ./ex2.sh && ls
....
2021-02-13-132m93.jpg   ex2.sh      file_1.txt  file_4.txt  

如果./ex2.sh返回状态码非0,后面的就不会执行。

逻辑与 (&&)

&&前面的语句返回的状态码为0时,执行后面的语句。

mkdir /tmp/bak && cp test.txt /tmp/bak

逻辑或 (||)

||前面的语句返回的状态码非0时(也就是执行失败),执行后面的语句。

cp test.txt /tmp/bak/ || cp test.test.txt /tmp

例如:

如果ping通了,就执行后面的输出。

#!/bin/bash
host="razeen.me"
ping -c 1 $host && echo "You can reach ${host}."

如果ping失败了,就执行后面的输出。

#!/bin/bash
host="google.com"
ping -c 1 $host || echo "You can't reach ${host}."

分号 (;)

分号不是一个逻辑运算符,但你可以用它来分割语句。

cp text.txt /tmp/bak/ ; cp test.txt /tmp
# 等同于
cp text.txt /tmp/bak/  
cp test.txt /tmp

管道 |

管道|两侧的命令在各自的子shell中运行,并且两者同时启动。

如下:

第一个命令将目录更改为主目录,并列出文件和目录。

第二个命令仅显示执行该命令的文件和目录。

$ echo "$(cd ~ && ls)"
$ echo "$(cd ~ | ls)"

函数

Bash中,你可以使用function或者直接定义一个函数。

function function-name(){}
# 或
function-name(){}

当你调用函数的时候,只需要函数名,不用带()

#!/bin/bash
function hello(){
  echo "Hello!"
}
# 正确
hello

# 错误
# hello()

在函数中,可以调用其他函数。

#!/bin/bash
function hello(){
  echo "Hello!"
  now
  bye
}
function now(){
  echo "It's $(date +%r)"
}
function bye(){
  echo "Bye bye."
}
hello

# Output
# Hello!
# It's 09:29:44 PM
# Bye bye.

但,需要注意函数的定义顺序。如果你在函数声明的前就去调用函数,函数就不会执行。如下, 在hello中执行now函数,但now是定义hello执行下面的,结果就会出错。

#!/bin/bash
# this won't work
function hello(){
  echo "Hello!"
  now
}
hello
function now(){
  echo "It's $(date +%r)"
}

输出:

$ ./hello2.sh
Hello!
./hello2.sh: line 5: now: command not found

函数传参

和脚本执行的时候传参一样,函数的参数也用$1…,$@ 来输出。

注意$0这里并不是函数的名字,而是当前脚本的名字。

$N是第N个参数,$@表示所有的参数。

#!/bin/bash
function fullname(){
  echo "$0"
  echo "My name is $1 $2"
}

fullname Razeen Cheng


# Output
# ./func.sh
# My name is Razeen Cheng
#!/bin/bash
function greeting(){
  for NAME in $@
  do
    echo "Hi $NAME."
  done
}

greeting Tom Jerry

变量的作用域

默认变量的作用域是全局的,必须先声明,后使用。 当然,最好在最上面就把需要的变量声明好。

#!/bin/bash
my_func() {
  GLOBAL_VAR=1
}
# 这时,变量还是空的
echo "Calling GLONAL_VAR before calling function my_func"
# echo $GLOBAL_VAR

# 声明后,就可以输出了
my_func
echo "Calling GLONAL_VAR after calling function my_func"
echo $GLOBAL_VAR

局部变量

可以用local来定义局部变量,且只能在函数中使用。

#!/bin/bash 
MY_VAR=1
my_func () {
  local MY_VAR=2
  echo "my_func: MY_VAR=$MY_VAR" 
}
echo "global: MY_VAR=$MY_VAR"
my_func

函数返回码

你可以在函数中,指定返回码:

函数中最后执行的命令的退出状态将隐式返回。 有效代码范围为0-255。0代表成功,$?可以显示退出码。

$ my_function
$ echo $?
0

可以在if 判断中用$?

#!/bin/bash

# 该函数用来创建一个备份文件
function backup_file () {
    local BACK # 声明局部变量
    if [[ -f $1 ]];then # 检查参数(是否是文件)
        BACK="/tmp/$(basename "$1").$(date +%F).$$"
        echo "Backing up $1 to $BACK"
        cp "$1" "$BACK"
    else
        # 文件不存在.
        return 1
    fi
}

# 调用函数
if [[ "$1" ]]; then
    backup_file "$1"
    # if [[ $? -eq 0 ]]; then
    if [[ $(backup_file "$1") -eq 0 ]]; then
        echo "Backup succeeded."
        exit 0
    else
        echo "Backup failed."
        # 备份失败,中断,并返回非0状态.
        exit 1
    fi
else
    backup_file /etc/hosts
    echo "/etc/hosts Backup succeeded."
    exit 0
fi

上面这个脚本默认备份/etc/hosts文件,除非你制定一个文件外。如果你指定一个文件参数,他会先检查文件,然后备份到/tmp目录。

$$ 返回 当前脚本执行的PID. 每次运行PID都会发生变化。当你需要多次运行脚本时,或许对你有帮助。

basename ${1} 可以从你输入的路径中提取文件的名字. 如 basename /etc/hostshosts.

$ ls /tmp
$ ex1           
Backing up /etc/hosts to /tmp/hosts.2020-10-04.77124
Backup succeeded.
$ ls /tmp
hosts.2020-10-04.77124

关键字 exit 和 return

return 会跳出当前函数, exit会结束当前脚本。

总结

这篇博客总结了常用的,我们需要了解的一些脚本语法与知识。如果向更好的使用bash, 我们还需要进一步学习更多的命令等。希望这篇博客能对你有所帮助。

参考

  1. Variables, Internal variables
  2. shellcheck
  3. shellcheck wiki
  4. shell-format
  5. The Bash Hackers Wiki
  6. Advanced Bash-Scripting Guide