Back
Featured image of post Shell学习

Shell学习

Shell 简介

Bash(Bourne Again SHell) 是 Unix 系统和 Linux 系统的一种命令环境。

Shell 是一个程序,它提供了用户与计算机交互的环境。其次,Shell 也是命令解释器,能够写一些简单的脚本。最后,Shell 也是一个工具箱,方便用户操作系统。

我们可以查看在 Linux 系统下的 Shell。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo $SHELL
/bin/bash

[root@iZ7xvfomazz4187zib4aurZ ~]# cat /etc/shells
/bin/sh
/bin/bash
/usr/bin/sh
/usr/bin/bash

命令行环境

进入命令行环境以后,用户会看到 Shell 的提示符。

# 用户名@主机名
[user@hostname] $

当然,如果用户是 root 那么会以 # 结尾,而不是以美元符号 $ 结尾。

一般来说,进入命令行环境后就已经打开 Bash 了,如果不是则可以用 bash 来启动,然后退出就是 exit 命令。

Bash 基本语法

echo 命令

echo 命令就相当于 print 命令的用法。

# 单行文本输出
[root@iZ7xvfomazz4187zib4aurZ ~]# echo Hello Yikuanzz
Hello Yikuanzz

# 多行文本输出
[root@iZ7xvfomazz4187zib4aurZ ~]# echo "<HTML>
>     <HEAD>
>           <TITLE>Page Title</TITLE>
>     </HEAD>
>     <BODY>
>           Page body.
>     </BODY>
> </HTML>"
<HTML>
    <HEAD>
          <TITLE>Page Title</TITLE>
    </HEAD>
    <BODY>
          Page body.
    </BODY>
</HTML>

-n 参数

默认情况下,echo 输出的文本末尾会有一个回车符号,-n 参数可以取消末尾的回车符。分号作为命令的结束符号。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo -n Hello Yikuanzz
Hello Yikuanzz[root@iZ7xvfomazz4187zib4aurZ ~]# 

[root@iZ7xvfomazz4187zib4aurZ ~]# echo hello;echo yikuanzz
hello
yikuanzz

-e 参数

-e 参数会解释引号里面的特殊字符。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo "Hello\nYikuanzz"
Hello\nYikuanzz

[root@iZ7xvfomazz4187zib4aurZ ~]# echo -e "Hello\nYikuanzz"
Hello
Yikuanzz

shopt 命令

shopt 命令用来调整 Bash 的行为:

  • shopt -s [optionname] 打开某个参数。
  • shopt -u [optionname] 打开某个参数。
  • shopt [optionname] 查询某个参数是关闭还是打开

dotglob 参数

dotglob参数可以让拓展结果包括隐藏文件(即 . 开头的文件)。

[root@iZ7xvfomazz4187zib4aurZ ~]# shopt dotglob
dotglob        	off
[root@iZ7xvfomazz4187zib4aurZ ~]# ls *
file.txt
[root@iZ7xvfomazz4187zib4aurZ ~]# shopt -s dotglob
[root@iZ7xvfomazz4187zib4aurZ ~]# shopt dotglob
dotglob        	on
[root@iZ7xvfomazz4187zib4aurZ ~]# ls *
.bash_history  .bashrc   .lesshst	file.txt

nullglob 参数

nullglob 参数可以让通配符不匹配任何文件名时,返回空字符。

[root@iZ7xvfomazz4187zib4aurZ ~]# shopt nullglob
nullglob       	off
[root@iZ7xvfomazz4187zib4aurZ ~]# rm b*
rm: cannot remove ‘b*’: No such file or directory
[root@iZ7xvfomazz4187zib4aurZ ~]# shopt -s nullglob
[root@iZ7xvfomazz4187zib4aurZ ~]# shopt nullglob
nullglob       	on
[root@iZ7xvfomazz4187zib4aurZ ~]# rm b*
rm: missing operand
Try 'rm --help' for more information.

failglob 参数

failglob 参数让通配符不匹配任何文件名时,Bash 会直接报错,而不是让命令去处理。

[root@iZ7xvfomazz4187zib4aurZ ~]# shopt -s failglob
[root@iZ7xvfomazz4187zib4aurZ ~]# rm b*
-bash: no match: b*

extglob 参数

extglob 参数让 Bash 支持 Korn SHell 的拓展语法,主要是量词语法。

量词语法有下面几个:

  • ?(pattern-list):匹配零个或一个模式;例如 ?(def) 表示匹配零个或一个 def
  • *(pattern-list):匹配零个或多个模式.
  • +(pattern-list):匹配一个或多个模式;例如 +(.txt|.php) 表示匹配 .txt.php
  • @(pattern-list):只匹配一个模式。
  • !(pattern-list):匹配零个或一个以上的模式,但不匹配单独一个的模式。

nocaseglob 参数

nocaseglob 参数可以让通配符拓展不区分大小写。

比如,打开后 program* 就不区分大小写了,可以匹配 ProgramData 等等。

globstar 参数

globstar 参数可以让 ** 匹配零个或多个子目录。

比如,我们用 ls **/*.txt 就可以匹配当前目录和多个子目录的 txt 文件。

read 命令

read 命令,它将用户的输入存入一个变量,它的语法格式为 read [-options][variable]。其中,options 是参数选项,variable 是用来保存输入数值的一个或多个变量名,如果没有提供变量名,环境变量 REPLY 会包含用户输入的一整行数据。

#!/usr/bin/env bash

echo -n "输入文本 >"
read text
echo "你的输入为: $text"

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

#!/usr/bin/env bash

while read myline
do 
	echo "$myline"
done < $filename

-t 参数

read命令的 -t参数,设置了超时的秒数。如果超过了指定时间,用户仍然没有输入,脚本将放弃等待,继续向下执行。或者我们也可以设置环境变量 TMOUT

#!/usr/bin/env bash

echo -n "输入文本 > "
if read -t 3 response; then
	echo "用户已输入"
else
	echo "用户未输入"
fi

-p 参数

read命令的 -p参数,指定用户输入的提示信息。

#!/usr/bin/env bash

read -p "Enter one or more values > "
echo "REPLY = '$REPLY'"

-a 参数

read命令的 -a参数把用户的输入赋值给一个数组,索引是从 0 开始的。

[root@iZ7xvfomazz4187zib4aurZ bin]# read -a response
Hello Yikuanzz
[root@iZ7xvfomazz4187zib4aurZ bin]# echo ${response[1]}
Yikuanzz

-n 参数

read命令的 -n 参数指定只读取若干字符作为变量值,而不是整行读取。

[root@iZ7xvfomazz4187zib4aurZ ~]# read -n 3 var
aklhgalg
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $var
akl

-e 参数

read命令的 -n 参数允许用户输入的时候,使用 readline 库提供的快捷键。

#!/usr/bin/env bash

echo "请输入文件地址:"

read -e fileName

echo $fileName

IFS 变量

read 命令读取的值,默认是以空格分隔,可以通过自定义环境变量 IFS (内部字段分隔符,Internal Field Separator ),修改分隔标志。

[root@iZ7xvfomazz4187zib4aurZ ~]# IFS='|'
[root@iZ7xvfomazz4187zib4aurZ ~]# text='aa|bbb|cccc'
[root@iZ7xvfomazz4187zib4aurZ ~]# for i in $text;do echo "i=$i"; done
i=aa
i=bbb
i=cccc

如果 IFS 设为空字符串,整行读入一个边量。

#!/usr/bin/env bash
input="/path/to/txt/file"
while IFS= read -r line
do
  echo "$line"
done < "$input"

条件判断 ifcase

ifelifelse 关键字。

if commands; then
	commands
[elif commands; then
	commands...]
[else
	commands]
fi	

一个小例子:

#!/usr/bin/env bash

echo -n "输入一个1到3之间的数字(包含两端)>"
read character
if [ "$character" = "1" ]; then
	echo 1
elif [ "$character" = "2" ]; then
	echo 2
elif [ "$character" = "3" ]; then
	echo 3
else
	echo "输入不符合要求"
fi

if 结构的判断条件中,一般使用 test 命令,有三种形式:test expression[ expression ][[ expression ]]


另一个常见的条件判断就是 case 结构了。

case expression in
	pattern )
		commands ;;
	pattern )
		commands ;;
	...
esac

一个小例子:

#!/usr/bin/env bash
echo -n "输入一个1到3之间的数字(包含两端)> "
read character
case $character in
  1 ) echo 1
    ;;
  2 ) echo 2
    ;;
  3 ) echo 3
    ;;
  * ) echo "输入不符合要求"
esac

文件判断

以下表达式用来判断文件状态。

  • [ -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

字符串判断

以下表达式用来判断字符串。

  • [ string ]:如果string不为空(长度大于0),则判断为真。
  • [ -n string ]:如果字符串string的长度大于零,则判断为真。
  • [ -z string ]:如果字符串string的长度为零,则判断为真。
  • [ string1 = string2 ]:如果string1string2相同,则判断为真。
  • [ string1 == string2 ] 等同于[ string1 = string2 ]
  • [ string1 != string2 ]:如果string1string2不相同,则判断为真。
  • [ string1 '>' string2 ]:如果按照字典顺序string1排列在string2之后,则判断为真。
  • [ string1 '<' string2 ]:如果按照字典顺序string1排列在string2之前,则判断为真。

整数判断

下面的表达式用于判断整数。

  • [ 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

正则判断

[[ expression ]] 支持正则表达式。

一般来说,它的语法格式是 [[ string1 =~ regex ]],其中 regex 是一个正则表达式,=~ 是正则比较运算符号。

#!/usr/bin/env bash
INT=-5
if [[ "$INT" =~ ^-?[0-9]+$ ]]; then
  echo "INT is an integer."
  exit 0
else
  echo "INT is not an integer." >&2
  exit 1
fi

循环 whileuntilfor

首先要介绍的是每个语言中,几乎都会有的关键字 breakcontinue

break 命令立即终止循环,程序继续执行循环块之后的语句。

continue 命令立即终止本轮循环,开始执行下一轮循环。

while 循环

while condition; do
	commands
done

一个小例子:

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

until 循环

until condition; do
	commands
done

因为 until 循环与 while 循环恰好相反,只要不符合判断条件,就不断循环执行指定的语句。

#!/usr/bin/env bash

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

until 的条件部分也可以是一个命令,表示命令执行成功之前,不断重复尝试。

until cp $1 $2; do
	echo 'Attempt to copy failed. waiting ...'
	sleep 5
done

如果我们写成 while 循环的形式,把条件设为否定。

while ! cp $1 $2; do
	echo 'Attempt to copy failed. waiting ...'
	sleep 5
done	

for ... in 循环

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

for variable in list
do
	commands
done

列表可以有字符串组成的列表、通配符产生的列表或者子命令执行产生的列表,我们可以看看下面的例子。

#!/usr/bin/env bash

# 字符串形式
for i in word1 word2 word3; do
	echo $i
done

# 通配符形式
for i in *.png; do
	ls -l $i
done

# 子命令形式
count=0
for i in $(car ~/.bash_profile); do
	count=$((count+1))
	echo "Word $count ($i) contains $(echo -n $i | wc -c) characters"
done

in list 部分可以省略,当省略后它相当于将脚本的所有参数 $@ 并入循环,但是为了可读性我们不会希望将它省略。

#!/usr/bin/env bash

for filename in "$@"; do
	echo "$filename"
done

for 循环

for (( expression1; expression2; expression3 )); do
	commands
done

其中,expression1 用来初始化循环条件,expression2 用来决定循环结束的条件,expression3 在每次循环迭代的末尾执行,用于更新值。

#!/usr/bin/env bash

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

select 结构

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

select name
[in list]
do
	commands
done

Bash 会对select依次进行下面的处理。

  1. select生成一个菜单,内容是列表 list 的每一项,并且每一项前面还有一个数字编号。
  2. Bash 提示用户选择一项,输入它的编号。
  3. 用户输入以后,Bash 会将该项的内容存在变量 name,该项的编号存入环境变量 REPLY。如果用户没有输入,就按回车键,Bash 会重新输出菜单,让用户选择。
  4. 执行命令体 commands
  5. 执行结束后,回到第一步,重复这个过程。

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

#!/usr/bin/env bash

echo "Which Operating System do you like?"

select os in Ubuntu LinuxMint Windows8 Windows7 WindowsXP
do
  case $os in
    "Ubuntu"|"LinuxMint")
      echo "I also use $os."
    ;;
    "Windows8" | "Windows10" | "WindowsXP")
      echo "Why don't you try Linux?"
    ;;
    *)
      echo "Invalid entry."
      break
    ;;
  esac
done

命令组合符号 &&||

Command1 && Command2 表示 Command1 执行成功后,继续执行 Command2

[root@iZ7xvfomazz4187zib4aurZ ~]# cat file.txt && ls -l file.txt
hello
-rw-r--r-- 1 root root 6 Apr 23 09:39 file.txt

Command1 || Command2 表示 Command1 执行失败后,才执行 Command2

[root@iZ7xvfomazz4187zib4aurZ ~]# mkdir foo || mkdir bar

此外,我们通过 type 可以知道命令是内置命令还是外部命令。

[root@iZ7xvfomazz4187zib4aurZ ~]# type echo
echo is a shell builtin
[root@iZ7xvfomazz4187zib4aurZ ~]# type mkdir
mkdir is hashed (/usr/bin/mkdir)

如果,我们给 type 加上 -t 参数,就可以返回一个命令的类型:别名(alias)、关键词(keyword)、函数(function)、内置命令(builtin)和文件(file)。

[root@iZ7xvfomazz4187zib4aurZ ~]# type -t type
builtin

Bash 模式扩展

Shell 接收到用户输入的命令以后,会根据空格将用户的输入,拆分成一个个词元(token)。然后,Shell 会扩展词元里面的特殊字符,扩展完成后才会调用相应的命令。

这种特殊字符的扩展,称为模式扩展(globbing)。其中有些用到通配符,又称为通配符扩展(wildcard expansion)。

我们可以通过 set -f 命令关闭拓展,set +f 命令打开拓展。

~ 拓展

我们使用 ~ 会自动拓展为当前用户的主目录。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo ~
/root

~/dir 表示拓展成主目录的某个子目录,注意该目录的实际地址并不一定在这个目录。

[root@iZ7xvfomazz4187zib4aurZ home]# echo ~/test
/root/test

~user 表示拓展成用户 user 的主目录,如果该用户不存在则不会拓展。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo ~root
/root

~+ 则会拓展为当前所在的目录。

[root@iZ7xvfomazz4187zib4aurZ test]# echo ~+
/home/test

? 拓展

? 代表文件路径里面的任意字符,这里是不包括空字符的。

[root@iZ7xvfomazz4187zib4aurZ test]# ls ??.txt
f1.txt  f2.txt  f3.txt

[root@iZ7xvfomazz4187zib4aurZ test]# echo ?.txt
a.txt b.txt

* 拓展

* 代表文件路径里面的任意数量字符,包括数量为零的情况。

[root@iZ7xvfomazz4187zib4aurZ test]# ll *.txt
-rw-r--r-- 1 root root 0 Apr 23 14:36 a.txt
-rw-r--r-- 1 root root 0 Apr 23 14:36 b.txt
-rw-r--r-- 2 root root 0 Feb 27 08:19 f1.txt
-rw-r--r-- 2 root root 0 Feb 27 08:19 f2.txt
lrwxrwxrwx 1 root root 6 Feb 27 08:28 f3.txt -> f1.txt

* 不会匹配隐藏文件,就是以 . 开头的文件。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo .*
. .. .bash_history .bash_logout .bash_profile .bashrc .cache .cshrc .docker .dotnet .lesshst .mysql_history .pip .pki .pydistutils.cfg .rnd .ssh .tcshrc .viminfo .vscode-server
[root@iZ7xvfomazz4187zib4aurZ ~]# echo .[!.]*
.bash_history .bash_logout .bash_profile .bashrc .cache .cshrc .docker .dotnet .lesshst .mysql_history .pip .pki .pydistutils.cfg .rnd .ssh .tcshrc .viminfo .vscode-server

[] 拓展

[] 表示选择 [...] 中的一个字符。

[root@iZ7xvfomazz4187zib4aurZ test]# echo [fab]*.txt
a.txt b.txt f1.txt f2.txt f3.txt

当然,我们可以使用 [^...][!...] 来匹配不在括号中的字符。

[root@iZ7xvfomazz4187zib4aurZ test]# echo ?[!3].txt
f1.txt f2.txt

注意,即使文件不存在也会进行拓展。与之相对,如果文件不存在,大部分的符号都不会拓展。

[start-end] 拓展

[start-end] 表示匹配一个连续的范围,比如 [0-5] 匹配 [012345]

常用的有:

  • [a-z]:所有小写字母。
  • [a-zA-Z]:所有小写字母与大写字母。
  • [a-zA-Z0-9]:所有小写字母、大写字母与数字。
[root@iZ7xvfomazz4187zib4aurZ test]# echo f[1-3].txt
f1.txt f2.txt f3.txt

{} 拓展

{} 表示分别拓展成大括号里面的所有值,各个值之间用逗号分隔,比如 {1,2,3} 拓展为 1 2 3

[root@iZ7xvfomazz4187zib4aurZ test]# echo hello-yikuan{1,2,3,4,5}
hello-yikuan1 hello-yikuan2 hello-yikuan3 hello-yikuan4 hello-yikuan5

[root@iZ7xvfomazz4187zib4aurZ test]# echo a{A{1,2},B{3,4}}b
aA1b aA2b aB3b aB4b

[root@iZ7xvfomazz4187zib4aurZ test]# echo {cat,f*}
cat f1.txt f2.txt f3.txt

{start..end} 拓展

{start..end} 表示拓展成一个连续的序列。比如,{a..z}可以扩展成26个小写英文字母。

[root@iZ7xvfomazz4187zib4aurZ test]# echo {a..z}
a b c d e f g h i j k l m n o p q r s t u v w x y z

[root@iZ7xvfomazz4187zib4aurZ test]# echo {001..11}
001 002 003 004 005 006 007 008 009 010 011

[root@iZ7xvfomazz4187zib4aurZ test]# echo {1..9..3}
1 4 7

常见的用途:

  • 新建一系列目录:mkdir {2007..2009}-{01..12}
  • 用于 for 循环:for i in {1..4}

### 拓展

Bash 将美元符号 $ 开头的词视为变量。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo $SHELL
/bin/bash

[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${SHELL}
/bin/bash

${!string*}${!string@} 返回所有给定字符串 string 的变量名。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${!S*}
SECONDS SHELL SHELLOPTS SHLVL SSH_CLIENT SSH_CONNECTION SSH_TTY

$(...) 拓展

$(...) 可以拓展为另外一个命令的运行结果。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo $(ls $(pwd))
dockerPratic file.txt foo go minikube-1.31.0-bp156.1.10.x86_64.rpm mysql80-community-release-el7-5.noarch.rpm nginx snap testdir

$((...)) 可以拓展成整数运算的结果。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo $((1+2))
3

[[:class:]] 拓展

[[:class:]]表示一个字符类,扩展成某一类特定字符之中的一个。常用的字符类如下。

  • [[:alnum:]]:匹配任意英文字母与数字
  • [[:alpha:]]:匹配任意英文字母
  • [[:blank:]]:空格和 Tab 键。
  • [[:cntrl:]]:ASCII 码 0-31 的不可打印字符。
  • [[:digit:]]:匹配任意数字 0-9。
  • [[:graph:]]:A-Z、a-z、0-9 和标点符号。
  • [[:lower:]]:匹配任意小写字母 a-z。
  • [[:print:]]:ASCII 码 32-127 的可打印字符。
  • [[:punct:]]:标点符号(除了 A-Z、a-z、0-9 的可打印字符)。
  • [[:space:]]:空格、Tab、LF(10)、VT(11)、FF(12)、CR(13)。
  • [[:upper:]]:匹配任意大写字母 A-Z。
  • [[:xdigit:]]:16进制字符(A-F、a-f、0-9)。

Bash 引号和转义

Bash 的数据类型只有字符串,所以与字符串相关的引号和转义就很重要。

转义

一般我们用 \ 来进行转义,当然,如果一些命令过长可以用 \ 来进行换行。

  • \b:退格。
  • \n:换行。
  • \r:回车。
  • \t:制表符。

单引号与双引号

与单引号不同的是,在双引号中,美元符号($)、反引号(`` )和反斜杠(`)会被自动拓展。

[root@iZ7xvfomazz4187zib4aurZ test]# echo "$(date)"
Wed Apr 24 23:50:35 CST 2024

因为双引号把换行符解释为普通字符,所以可以利用双引号在命令行输入多行文本。

[root@iZ7xvfomazz4187zib4aurZ test]# echo "hello
> Yikuanzz"
hello
Yikuanzz

此外,双引号一般用于处理文件名包含空格的文件。

当然,双引号还可以保存原始命令的输出格式。

[root@iZ7xvfomazz4187zib4aurZ test]# echo $(cal)
April 2024 Su Mo Tu We Th Fr Sa 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
[root@iZ7xvfomazz4187zib4aurZ test]# echo "$(cal)"
     April 2024     
Su Mo Tu We Th Fr Sa
    1  2  3  4  5  6
 7  8  9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30

Here 文档

Here 文档(here document)是一种输入多行字符串的方法,它以 << token 作为开始标记,以 token 作为结束标记。

[root@iZ7xvfomazz4187zib4aurZ test]# << _EOF_
> Hello
> Yikuanzz
> _EOF_

注意,在 Here 文档内部会发生变量替换和通配符拓展,但是单引号和双引号都会失去语法作用。

如果你不想发生变量替换和通配符拓展,那么就把 token 用单引号引起来:'token'

Here 文档的一种变体:Here 字符串(Here string),使用三个小于号(<<<)表示。

[root@iZ7xvfomazz4187zib4aurZ test]# cat <<< "Hello Yikuanzz"
Hello Yikuanzz

Bash 变量

Bash 变量主要有环境变量和自定义变量两种。

环境变量就是 Bash 环境自带的一些变量:

[root@iZ7xvfomazz4187zib4aurZ test]# printenv
XDG_SESSION_ID=17169
HOSTNAME=iZ7xvfomazz4187zib4aurZ
TERM=xterm
SHELL=/bin/bash
HISTSIZE=1000
SSH_CLIENT=119.133.4.44 3586 22
SSH_TTY=/dev/pts/0
USER=root
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.axa=01;36:*.oga=01;36:*.spx=01;36:*.xspf=01;36:
MAIL=/var/spool/mail/root
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/var/lib/snapd/snap/bin:/root/bin
PWD=/home/test
LANG=en_US.UTF-8
HISTCONTROL=ignoredups
SHLVL=1
HOME=/root
LOGNAME=root
XDG_DATA_DIRS=/usr/local/share:/usr/share:/var/lib/snapd/desktop
SSH_CONNECTION=119.133.4.44 3586 172.16.195.53 22
LESSOPEN=||/usr/bin/lesspipe.sh %s
XDG_RUNTIME_DIR=/run/user/0
_=/usr/bin/printenv
OLDPWD=/home

创建变量

用户创建变量语法为 variable=value,变量名必须遵守下面的规则:

  • 字母、数字和下划线字符组成。
  • 第一个字符必须是一个字母或一个下划线,不能是数字。
  • 不允许出现空格和标点符号。
[root@iZ7xvfomazz4187zib4aurZ test]# b=$(pwd)

读取变量

在变量名前使用 $ 就能够读取变量名的值。

[root@iZ7xvfomazz4187zib4aurZ test]# echo $b
/home/test

这里要注意的是如果变量的值也是变量,可以用 ${!varname} 的语法将其展开获得最终的值。

删除变量

unset 命令用来删除一个变量。

[root@iZ7xvfomazz4187zib4aurZ test]# unset b
[root@iZ7xvfomazz4187zib4aurZ test]# echo $b

[root@iZ7xvfomazz4187zib4aurZ test]# 

输出变量

export 用来向子 Shell 输出变量。

[root@iZ7xvfomazz4187zib4aurZ ~]# NAME_=Yikuanzz
[root@iZ7xvfomazz4187zib4aurZ ~]# export NAME_

特殊变量

$? 为上一个命令的退出码,用来判断上个命令是否执行成功。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo $?
127

$$ 为当前 Shell 的进程ID,当然它一般用来命名临时文件比如 LOGFILE=/tmp/output_log.$$

[root@iZ7xvfomazz4187zib4aurZ ~]# echo $$
30290
[root@iZ7xvfomazz4187zib4aurZ ~]# bash
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $$
30855

$_ 为上一个命令的最后一个参数。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo $_
/etc/bashrc

$! 为最近一个后台执行的异步命令的进程 ID。

[root@iZ7xvfomazz4187zib4aurZ ~]# bash &
[1] 31071
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $!
31071

[1]+  Stopped                 bash

$0 为当前 Shell 的名称。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo $0
bash

$- 为当前 Shell 的启动参数。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo $-
himBH

$@$# 表示脚本的参数数量。

变量默认值

Bash 提供了特殊的语法来保证变量不为空:

  • ${varname:-word} 表示如果变量 varname 存在且不为空,则返回它的值,否则返回word
  • ${varname:=word} 表示如果变量 varname存在且不为空,则返回它的值,否则将它设为word
  • ${varname:+word} 如果变量名存在且不为空,则返回word,否则返回空值。
  • ${varname:?message} 如果变量 varname存在且不为空,则返回它的值,否则打印出 varname: message,并中断脚本的执行。如果省略了 message,则输出默认的信息“parameter null or not set.”。

上面四种语法如果用在脚本中,变量名的部分可以用到数字 19,表示脚本的参数。

比如:filename=${1:?"filename missing."}

上面代码出现在脚本中,1表示脚本的第一个参数。如果该参数不存在,就退出脚本并报错。

declare 命令

declare 命令可以声明一些特殊类型的变量,为变量设置一些限制,比如声明只读类型的变量和整数类型的变量。

它的语法就是:declare OPTION VARIABLE=value

declare命令的主要参数(OPTION)如下。

  • -a:声明数组变量。
  • -f:输出所有函数定义。
  • -F:输出所有函数名。
  • -i:声明整数变量。
  • -l:声明变量为小写字母。
  • -p:查看变量信息。
  • -r:声明只读变量,不能 unset
  • -u:声明变量为大写字母。
  • -x:该变量输出为环境变量,等同于 export 命令。

declare命令如果用在函数中,声明的变量只在函数内部有效,等同于 local命令。

下面是一些例子:

-i 声明变量为整数。

[root@iZ7xvfomazz4187zib4aurZ ~]# declare -i v1=12 v2=3
[root@iZ7xvfomazz4187zib4aurZ ~]# declare -i res
[root@iZ7xvfomazz4187zib4aurZ ~]# res=v1*v2
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $res
36

-u 可以自动将变量转成大写字母。

[root@iZ7xvfomazz4187zib4aurZ ~]# declare -u v3
[root@iZ7xvfomazz4187zib4aurZ ~]# v3=upper
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $v3
UPPER

readonly 命令

readonly命令等同于 declare -r,用来声明只读变量,不能改变变量值,也不能 unset变量。

[root@iZ7xvfomazz4187zib4aurZ ~]# readonly v4=1
[root@iZ7xvfomazz4187zib4aurZ ~]# v4=2
bash: v4: readonly variable

readonly命令有三个参数。

  • -f:声明的变量为函数名。
  • -p:打印出所有的只读变量。
  • -a:声明的变量为数组。

let 命令

let命令声明变量时,可以直接执行算术表达式。

[root@iZ7xvfomazz4187zib4aurZ ~]# let v5=1+2+3
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $v5
6

let 命令还可以对多个变量进行赋值。

[root@iZ7xvfomazz4187zib4aurZ ~]# let "r1=2" "r2 = r1++"
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $r1,$r2
3,2

Bash 函数

函数(function)是可以重复使用的代码片段,有利于代码的复用。

函数总是在当前 Shell 执行,这是跟脚本的一个重大区别,Bash 会新建一个子 Shell 执行脚本。

如果函数与脚本同名,函数会优先执行。但是,函数的优先级不如别名,即如果函数与别名同名,那么别名优先执行。

简单定义

函数定义的语法有两种:

# 第一种
fn() {
	# codes
}

# 第二种
function fn(){
	# codes
}

比如,我们在 Shell 中写下这样一个函数 hello() { echo "Hello $1"}

那么我们在命令行中调用它,就会是这样的。

[root@iZ7xvfomazz4187zib4aurZ bin]# hello(){
> echo "Hellp $1"
> }
[root@iZ7xvfomazz4187zib4aurZ bin]# hello world
Hellp world

如果我们想删除一个函数,可以用 unset 命令,它的语法是这样的 unset -f functionName

我们还可以用 declare -f 查看已经定义好的所有函数。

参数变量

函数的参数变脸,与脚本参数变量是一致的。

  • $1 ~ $9:函数的第一个到第9个的参数。
  • $0:函数所在的脚本名。
  • $#:函数的参数总数。
  • $@:函数的全部参数,参数之间使用空格分隔。
  • $*:函数的全部参数,参数之间使用变量 $IFS值的第一个字符分隔,默认为空格,但是可以自定义。

如果函数的参数多于9个,那么第10个参数可以用 ${10}的形式引用,以此类推。

#!/usr/bin/env bash

function log_msg{
	echo "[ `date '+ %F %T'` ]: $@"
}

return 命令

return 命令用于从函数返回一个值,函数执行到这条命令后,就不再继续执行,直接返回。

假设我们有这个函数 function func_return_value { return 10 },调用之后我们可以用 $? 拿到返回值 func_return_value; echo "Value returned by function is: $?"

全局变量和局部变量

与其他语言有区别的是,Bash 函数体内直接声明的变量,属于全局变量,整个脚本都可以读取。

#!/usr/bin/env bash

fn () {
	foo=1
	echo "fn: foo = $foo"
}

fn
echo "global: foo = $foo"

我们可以在函数里面使用 local 命令来声明局部变量。

#!/usr/bin/env bash

fn () {
	local foo
	foo=1
	echo "fn: foo = $foo"
}

fn
echo "global: foo = $foo"

Bash 数组

数组(array)就是我们所熟悉的那个数组,当然它更像一个列表,元素的下标也是从 0 开始的。

创建数组

我们通过赋值的方法创建数组。

# 逐个赋值
array[0]=val
array[1]=val
array[2]=val

# 一次性赋值
ARRAY=(value1 value2 ... valueN)

此外,我们创建数组的时候可以在每个值前面指定位置。

days=(Sun Mon Tue Wed Thu Fri Sat)
days=([0]=Sun [1]=Mon [2]=Tue [3]=Wed [4]=Thu [5]=Fri [6]=Sat)

或者为某些值指定位置,也是可以的。

names=(hatter [5]=duchess alice)

hatter 是数组的 0 号位置,duchess 是 5 号位置,alice 是 6 号位置。

定义数组的时候,可以使用通配符。

mp3s=( *.mp3 )

另外的方式有 declare -a ARRARYNAMEread -a ARRARYNAME

读取数组

读取数组指定位置的成员,使用 ${array[i]}

如果我们想读取所有成员的话,需要用到 @* 的特殊索引,比如 ${array[@]}

当我们配合 for 循环语句执行的时候,@* 是有差别的,差别就在于双引号。

#!/usr/bin/env bash

names=( Mike "Glibe robot" Tim "Jame Hardon" susan)
for name in ${names[@]}; do
	echo "Name: $name"
done

如果使用 @ 并且不使用 "" 的话,它的输出会是这样的。当然,使用 * 的话是一样的。

[root@iZ7xvfomazz4187zib4aurZ bin]# source name.sh 
Name: Mike
Name: Glibe
Name: robot
Name: Tim
Name: Jame
Name: Hardon
Name: susan

如果使用 @ 并且使用 "" 的话,它的输出会是这样的。

[root@iZ7xvfomazz4187zib4aurZ bin]# source name.sh 
Name: Mike
Name: Glibe robot
Name: Tim
Name: Jame Hardon
Name: susan

如果用 * 并且用 "" 的话,它的输出就会是这样的,将所有元素变成一个字符串输出。

[root@iZ7xvfomazz4187zib4aurZ bin]# source name.sh 
Name: Mike Glibe robot Tim Jame Hardon susan

数组拷贝

如果我们想拷贝一个数组,我们可以这样写:copyArrary=( "${names[$@]}" )

数组长度

我们可以用这两种语法:${#array[*]}${#array[@]}

如果我们用这种语法读取成员的话,就会得到成员字符串的长度,比如我们用 ${#a[100]}

成员下标

${!array[@]}${!array[*]},可以返回数组的成员序号,即哪些位置是有值的。

[root@iZ7xvfomazz4187zib4aurZ ~]# arr=([5]=a [9]=b [23]=c)
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${!arr[@]}
5 9 23
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${!arr[*]}
5 9 23

当然,我们也可以通过 for 循环语句来遍历数组。

此外,我们可以通过切片的方式来去提取数组成员,它的语法是 ${array[@]:position:length}

[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]}
a b c d
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]:1:3}
b c d

追加成员

数组末尾追加成员,可以使用 +=赋值运算符。它能够自动地把值追加到数组末尾。

[root@iZ7xvfomazz4187zib4aurZ ~]# arr+=(e f g)
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]}
a b c d e f g

删除数组

unset 命令删除一个数组成员,当然删除成员也可以将这个成员设为空值。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]}
a b c d e f g
[root@iZ7xvfomazz4187zib4aurZ ~]# unset arr[0]
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]}
b c d e f g
[root@iZ7xvfomazz4187zib4aurZ ~]# arr[1]=''
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]}
c d e f g

如果我们用 unset ArrayName 命令可以清空整个数组。

[root@iZ7xvfomazz4187zib4aurZ ~]# unset arr
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${arr[@]}

关联数组

关联数组使用字符串而不是整数作为数组索引。

[root@iZ7xvfomazz4187zib4aurZ ~]# declare -A colors
[root@iZ7xvfomazz4187zib4aurZ ~]# colors["red"]="#ff0000"
[root@iZ7xvfomazz4187zib4aurZ ~]# colors["green"]="#00ff00"
[root@iZ7xvfomazz4187zib4aurZ ~]# colors["blue"]="#0000ff"

Bash 字符串操作

字符串长度

通过 ${#varname} 查看字符串的长度。

[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${#v3}
5

子字符串

通过 ${varname:offset:length} 来提取子串,这个方法只能对变量进行操作。

[root@iZ7xvfomazz4187zib4aurZ ~]# NAME=Yikuanzz
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${NAME:0:6}
Yikuan
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${NAME: -4:4}
anzz

搜索和替换

字符串头部的模式匹配

${variable#pattern} 如果 pattern 匹配变量 variable 的开头,则会删除最短匹配的部分,将剩余部分返回。

${variable##pattern} 如果 pattern 匹配变量 variable 的开头,则会删除最长匹配的部分,将剩余部分返回。

[root@iZ7xvfomazz4187zib4aurZ ~]# myPath=/home/cam/book/long.file.name
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${myPath#/*/}
cam/book/long.file.name
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${myPath##/*/}
long.file.name

我们注意到 ${variable#pattern} 模式下 /*/ 匹配到的是 /home/;而 ${variable##pattern} 模式下 /*/ 匹配到的是 /home/cam/book/

此外,如果要将头部匹配的部分替换成其他内容,可以用 ${variable/#pattern/string} 的语法。

[root@iZ7xvfomazz4187zib4aurZ ~]# FF=JPG.JPG
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${FF/#JPG/jpg}
jpg.JPG

字符串尾部的模式匹配

${variable%pattern} 如果 pattern 匹配变量 variable 的结尾,则会删除最短匹配的部分,将剩余部分返回。

${variable%%pattern} 如果 pattern 匹配变量 variable 的结尾,则会删除最长匹配的部分,将剩余部分返回。

[root@iZ7xvfomazz4187zib4aurZ ~]# path=/home/cam/book/long.file.name
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${path%.*}
/home/cam/book/long.file
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${path%%.*}
/home/cam/book/long

此外,如果要将尾部匹配的部分替换成其他内容,可以用 ${variable/%pattern/string} 的语法。

[root@iZ7xvfomazz4187zib4aurZ ~]# FF=JPG.JPG
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${FF/%JPG/jpg}
JPG.jpg

任意位置的模式匹配

${variable/pattern/string} 如果 pattern 匹配变量 variable 的一部分,最长匹配的那部分会被 string 替换,但仅替换第一个匹配的。

${variable//pattern/string} 如果 pattern 匹配变量 variable 的一部分,最长匹配的那部分会被 string 替换,并且所有匹配的部分都会替换。

[root@iZ7xvfomazz4187zib4aurZ ~]# path=/home/cam/foo/foo.name
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $path
/home/cam/foo/foo.name
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${path/foo/bar}
/home/cam/bar/foo.name
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${path//foo/bar}
/home/cam/bar/bar.name

改变大小写

通过 ${varname^^} 语法可以将变量值转为大写。

通过 ${varname,,} 语法可以将变量值转为小写。

[root@iZ7xvfomazz4187zib4aurZ ~]# NAME=YikuanZz
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${NAME^^}
YIKUANZZ
[root@iZ7xvfomazz4187zib4aurZ ~]# echo ${NAME,,}
yikuanzz

Bash 算术运算

算术表达式

在之前我们有提到使用 ((...)) 语法可以进行整数运算,这意味着我们可以使用加、减、乘、除、取余、指数等等运算。

此外使用 $((...)) 的语法可以在内部用圆括号改变运算顺序,并且可以使用变量,如果变量不存在会被视为零。

[root@iZ7xvfomazz4187zib4aurZ ~]# num=123
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $(((num-23)*10))
1000

数值的进制

Bash 数值默认是十进制,但是在算术表达式中,也可以使用其他进制。

  • number:没有任何特殊表示法的数字是十进制数(以10为底)。
  • 0number:八进制数。
  • 0xnumber:十六进制数。
  • base#numberbase进制的数。

位运算

$((...))支持以下的二进制位运算符。

  • <<:位左移运算,把一个数字的所有位向左移动指定的位。
  • >>:位右移运算,把一个数字的所有位向右移动指定的位。
  • &:位的“与”运算,对两个数字的所有位执行一个AND操作。
  • |:位的“或”运算,对两个数字的所有位执行一个OR操作。
  • ~:位的“否”运算,对一个数字的所有位取反。
  • !:逻辑“否”运算
  • ^:位的异或运算(exclusive or),对两个数字的所有位执行一个异或操作。

逻辑运算

$((...))支持以下的逻辑运算符。

  • <:小于
  • >:大于
  • <=:小于或相等
  • >=:大于或相等
  • ==:相等
  • !=:不相等
  • &&:逻辑与
  • ||:逻辑或
  • expr1?expr2:expr3:三元条件运算符。若表达式expr1的计算结果为非零值(算术真),则执行表达式expr2,否则执行表达式expr3

expr 命令

expr 命令支持算术运算和变量替换,但是不支持非整数运算。

Bash 脚本入门

脚本(script)就是包含一系列命令的一个文本文件。Shell 读取这个文件,依次执行里面的所有命令,就好像这些命令直接输入到命令行一样。

Shebang 行

脚本的第一行通常是指定解释器,即这个脚本必须通过什么解释器执行。这一行以#!字符开头,这个字符称为 Shebang,所以这一行就叫做 Shebang 行。

#!后面就是脚本解释器的位置,Bash 脚本的解释器一般是/bin/sh/bin/bash

如果 Bash 解释器不放在目录/bin,脚本就无法执行了。为了保险,可以写成这样:#!/usr/bin/env bash

如果没有 Shebang 行,就可能要手动将脚本传给解释器来执行。

env 命令 总是指向 /usr/bin/env 文件,或者说这个二进制文件总是在目录 /usr/bin 中。

#!/usr/bin/env NAME这个语法的意思是,让 Shell 查找$PATH环境变量里面第一个匹配的NAME。如果你不知道某个命令的具体路径,或者希望兼容其他用户的机器,这样的写法就很有用。

脚本路径

我们除了要给脚本权限外,还要指定脚本的路径。

如果我们将脚本放在环境变量 $PATH 指定的目录中,就不需要指定路径了。

建议在主目录新建一个~/bin子目录,专门存放可执行脚本,然后把~/bin加入到$PATH 的末尾,

[root@iZ7xvfomazz4187zib4aurZ ~]# export PATH=$PATH:~/bin
[root@iZ7xvfomazz4187zib4aurZ ~]# source ~/.bashrc
[root@iZ7xvfomazz4187zib4aurZ ~]# echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/var/lib/snapd/snap/bin:/root/bin:/root/bin

脚本参数

在调用脚本的时候,文件名后可以带有参数,比如 script.sh word1 word2 word3

脚本文件内部,可以用特殊变量引用这些参数:

  • $0:脚本文件名,即 script.sh
  • $1~$9:对应脚本的第一个参数到第九个参数。
  • $#:参数的总数。
  • $@:全部的参数,参数之间使用空格分隔。
  • $*:全部的参数,参数之间使用变量 $IFS值的第一个字符分隔,默认为空格,但是可以自定义。

如果脚本的参数多于9个,那么第10个参数可以用 ${10}的形式引用,以此类推。

shift 命令 可以改变脚本参数,每次执行都会移除脚本当前的第一个参数,让后面的参数向前,以此类推。此外也可以移除多个参数,比如 shift 3 就可以移除前三个参数。

#!/usr/bin/env bash

echo "一共输入了 &# 个参数"

while ["$1" != ""]; do
	echo "剩下 $# 个参数"
	echo "参数:$1"
	shift
done

getopts 命令 用在脚本内部,可以解析复杂的脚本命令行参数,取出脚本所有带有前置连词线 - 的参数。

语法为 getopts optstring name 其中第一参数为所有连词线参数,第二个参数为变量名用于储存当前取到的配置项参数。

假设,某个脚本参数有 -l-h-a ,其中 -a 是可以带参数的,那么optstring 可以写为 lha:

while getopts 'lha:' OPTION; do
  case "$OPTION" in
  	l)
  		echo "linuxconfig"
  		;;
  	h)
  		echo "h stands for h"
  		;;
  	a)
  		avalue="$OPTARG"
  		echo "The value provided is $OPTARG"
  		;;
  	?)
  		echo "script usage: $(basement $0)[-l][-h][-a somevalue]" >& 2
  		# 退出值为 0 表示正常;1 表示错误;2 表示用法不对
  		exit 1
  		;;
  esac
done
# $OPTIND 在 getopts 前为 1 每次执行都会加 1
shift "$(($OPTIND - 1))"

-- 参数终止符 可以让指定变量作为实体参数,而不是配置项参数。

比如,myPath="~/docs" ls --$myPath 就会将其作为实体参数解释。

命令执行结果

命令执行结束后,会有一个返回值。0表示执行成功,非 0(通常是 1)表示执行失败。环境变量 $? 可以读取前一个命令的返回值。

#!/usr/bin/env bash

cd $some_directory
if ["$?" = "0"]; then
	rm *
else
	echo "无法切换目录!" 1>&2
	exit 1
fi

更简洁的写法是:

cd $some_directory && rm *
cd $some_directory || exit 1

source 命令

source 命令用于执行一个脚本,通常用于重新加载一个配置文件。

比如,source .bashrc

它会在当前 Shell 中执行脚本,如果用 bash 则会新建一个 子Shell 来执行。

因此,用 source执行脚本的话,就可以用当前 Shell 的变量;如果用 bash 的话,就需要将变量 export 到 子Shell 才能使用。


此外,source 命令的另一个用途是在脚本内部加载外部库。

比如,source ./lib.sh 或者 ..bashrc

alias 命令

alias 命令用来为一个命令指定别名,它的语法是 alias NAME=DEFINITION

比如,alias search=grep

我们可以通过 alias 命令查看所有别名。

通过 unalias 命令解除别名。

Built with Hugo
Theme Stack designed by Jimmy
© Licensed Under CC BY-NC-SA 4.0