Shell教程

Shell教程

Shell 脚本(shell script),是一种为 shell 编写的脚本程序。

业界所说的shell通常都是指shell脚本,但读者朋友要知道,shell和shell script是两个不同的概念。

由于习惯的原因,简洁起见,本文出现的"shell编程"都是指shell脚本编程,不是指开发 shell 自身。

注意 bashshell 有差异

Shell脚本

Shell 环境

Shell 编程跟 java、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了。

Linux 的 Shell 种类众多,常见的有:

  • Bourne Shell(/usr/bin/sh或/bin/sh)
  • Bourne Again Shell(/bin/bash)
  • C Shell(/usr/bin/csh)
  • K Shell(/usr/bin/ksh)
  • Shell for Root(/sbin/sh)
  • ……

本教程关注的是 Bash,也就是 Bourne Again Shell,由于易用和免费,Bash 在日常工作中被广泛使用。同时,Bash 也是大多数 Linux 系统默认的 Shell。

在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像 #!/bin/sh,它同样也可以改为**#!/bin/bash**。

#!告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序。

和其他语言一样,Shell 也可以包含外部脚本。这样可以很方便的封装一些公用的代码作为一个独立的文件。

创建Shell脚本

打开文本编辑器(可以使用 vi/vim 命令来创建文件),新建一个文件 test.sh,扩展名为 shsh代表shell),扩展名并不影响脚本执行,见名知意就好。

文件样例:

#!/bin/bash
echo "Hello World !"

执行Shell文件

chmod +x ./test.sh  #使脚本具有执行权限
. test.sh   # 注意点号(.)和文件名中间有一空格
# or
source test.sh

基础命令

echo 命令

echo 用于输出字符串

输出普通字符串

echo "Test output str" # 推荐
# or
echo Test output str

转义字符

echo "\"It is a test\""

输出变量

echo "$name It is a test"

不输出变量

echo '$name\"'

结果定向文件

  echo "It is a test" > myfile

输出命令执行结果

echo $(date) # 推荐
# or
echo `date`

prinf 命令

printf 命令模仿 C 程序库(library)里的 printf() 程序。

prinf命令语法:

printf  format-string  [arguments...]
printf "%-10s %-8s %-4s\n" 姓名 性别 体重kg  
printf "%-10s %-8s %-4.2f\n" 郭靖 男 66.1234 

输出:

姓名     性别   体重kg
郭靖     男      66.12

test 命令、[]符号

Shell中的 test 命令用于检查某个条件是否成立,它可以进行数值、字符和文件三个方面的测试。

数值测试
参数说明
-eq等于则为真
-ne不等于则为真
-gt大于则为真
-ge大于等于则为真
-lt小于则为真
-le小于等于则为真

Sample:

num1=100
num2=100
if test $[num1] -eq $[num2]
then
    echo '两个数相等!'
else
    echo '两个数不相等!'
fi

输出:

两个数相等!
字符串测试
参数说明
=等于则为真
!=不相等则为真
-z 字符串字符串为空或长度为0则为真
-n 字符串字符串长度不为零则为真

用例:

num1="HelloWorld"
num2="HelloWorld"
if test num1=num2
then
    echo '两个字符串相等!'
else
    echo '两个字符串不相等!'
fi

输出:

两个字符串相等!
文件测试
参数说明
-e 文件名如果文件存在则为真
-r 文件名如果文件存在且可读则为真
-w 文件名如果文件存在且可写则为真
-x 文件名如果文件存在且可执行则为真
-s 文件名如果文件存在且至少有一个字符则为真
-d 文件名如果文件存在且为目录则为真
-f 文件名如果文件存在且为普通文件则为真
-c 文件名如果文件存在且为字符型特殊文件则为真
-b 文件名如果文件存在且为块特殊文件则为真
否定

可以在判断之前使用!否定条件

if [ ! -z "$(ls .)" ]; then echo a; fi

注意事项:

  • 参数太多则会报错

    if [ -z $(ls .) ]; then echo "test"; fi
    

    输出:

    bash: [: 参数太多
    

    这时需要使用""双引号表示

    if [ -z "$(ls .)" ]; then echo "test"; fi
    
  • 数值为空或长度为0比较长度不为0的值也会报错。

    bash: ((: == 1 : 语法错误:需要操作数(错误记号是 "== 值 "

    这时需要使用-z-n测试字符串。

    if [ -z "$(ls .)" ]; then echo a; fi
    

let 命令

let expression [expression …]

let内置命令允许对 shell 变量执行算术。每个表达式都按照下面 Shell 算法中给出的规则进行计算。如果最后一个表达式的计算结果为0,let 返回1; 否则返回0。

read 命令

read [-ers] [-a aname] [-d delim] [-i text] [-n nchars]
[-N nchars] [-p prompt] [-t timeout] [-u fd] [name …]

从标准输入读取一行,并将其分割为不同的字段。

从标准输入读取单独的一行,或者如果使用了 -u 选项,从文件描述符 中
读取。该行会被分割成字段,如同分割词语一样,并且第一个词被赋值给第一个
<名称>,第二个词被赋值给第二个 <名称>,以此类推,剩下所有的词被赋值给
最后一个 <名称>。只有 $IFS 中的字符会被视为词语分隔符。默认情况下,
反斜杠字符可以转义分隔符和换行符。

选项:
-a 数组 将词语按顺序赋值给 <数组> 变量的各个成员,索引从零开始
-d 分隔符 继续读取,直到遇到 <分隔符> 的第一个字符,而不是换行符
-e 使用 Readline 获取行
-i 文本 使用 <文本> 作为 Readline 的初始文字
-n 字符数 读取 <字符数> 个字符之后返回,而不是等到读取换行符。
但是如果读取了不到 <字符数> 个字符就遇到了分隔符,
则分隔符仍然生效
-N 字符数 仅在恰好读取了 <字符数> 个字符之后返回,除非遇到 EOF
或者读取超时。忽略所有的分隔符
-p 提示符 在尝试进行读取之前先输出 <提示符>(不加换行)
-r 不允许反斜杠转义任何字符
-s 不回显来自终端的输入
-t 超时 如果在 <超时> 秒内没有读取一个完整的行则超时并且返回失败。
默认的超时时间是 TMOUT 变量的值。<超时> 可以是小数。
如果 <超时> 是 0,read 会立即返回而不尝试读取任何数据,
且仅当可以从指定的文件描述符获取输入时,才返回成功。
如果超过了超时时间,则退出状态大于 128
-u fd 从文件描述符 中读取,而不是标准输入


expr命令

expr 表达式

将表达式的值列印到标准输出,分隔符下面的空行可提升算式优先级。

除了常规的运算外,还支持:

运算描述
字符串 : 表达式定位字符串中匹配表达式的模式
match 字符串 表达式“字符串 :表达式”
substr 字符串 偏移量 长度替换字符串的子串,偏移的数值从 1 起计
index 字符串 字符在字符串中发现字符的地方建立下标,或者标0
length 字符串字符串的长度
expr aaa : 'a\+'3
expr abc : 'a\(.\)c'
⇒ b
expr index abcdef cz
⇒ 3
expr index index a
error→ expr: syntax error
expr index + index a
⇒ 0

sleep命令

sleep NUMBER[SUFFIX]...

暂停NUMBER秒,SUFFIX可以是s秒(默认)、m分、h时、d日, NUMBER可以用浮点数。


xargs命令

xargs [选项]... 命令 [初始参数]...

以所给<初始选项>和其它更多来自标准输入的参数运行指定<命令>。

xargs可以把从标准输入读取的数据假设为一个名称,默认为{},这是我最喜欢的一个功能。

ls /|xargs -i echo {}

这将输出所有根目录下的目录或文件。

ls|xargs -i mv {} dist

移动当前目录下的所有文件到dist目录


变量

定义变量

your_name="HelloWorld"

变量名的命名须遵循如下规则:

  • 首个字符必须为字母(a-z,A-Z)。
  • 中间不能有空格,可以使用下划线(_)。
  • 不能使用标点符号。
  • 不能使用bash里的关键字(可用help命令查看保留关键字)。

也可以使用declare命令来声明变量,且declare声明变量具有更多可选项。

declare [-aAfFgiIlnrtux] [-p] [name[=value]]

还可以使用local进行定义。

local [option] name[=value]

移除变量

unset [-fnv] [name]

可以使用unset 来移除变量,如果给定-v 选项,则每个名称都引用一个shell 变量,并删除该变
量。如果给定-f 选项,则名称引用 shell 函数,并删除函数定义。如果提供了 -n 选项,并且
name 是具有 nameref 属性的变量,则 name 将被取消设置,而不是它引用的变量。

使用变量

echo ${your_name} # 推荐
echo $your_name 

字符串

your_name='qinjx'
str="Hello, I know your are \"$your_name\"! \n"

拼接字符串

your_name="qinjx"
greeting="hello, "$your_name" !"
greeting_1="hello, ${your_name} !"
echo $greeting $greeting_1

提取子字符串

string="alibaba is a great company"
echo ${string:1:4} #输出liba

查找字符串

string="alibaba is a great company"
echo `expr index "$string" great`

获取字符串长度

str="HelloWorld"
echo ${#str}

数组

定义数组

array_name=(value0 value1 value2 value3)

单独定义数组各个分量

array_name[0]=value0
array_name[1]=value1
array_name[n]=valuen

可以不使用连续的下标,而且下标的范围没有限制。

读取数组

获取数组指定 下标元素

valuen=${array_name[n]}

获取数组中的所有元素

echo ${array_name[@]}

获取数组的长度

# 取得数组元素的个数
length=${#array_name[@]}
# 或者
length=${#array_name[*]}
# 取得数组单个元素的长度,类似字符串获取长度
lengthn=${#array_name[n]}

传递参数

我们可以在执行 Shell 脚本时,向脚本传递参数,脚本内获取参数的格式为:$nn 代表一个数字,1 为执行脚本的第一个参数,2 为执行脚本的第二个参数,以此类推……

test.sh

#!/bin/bash

echo "Shell 传递参数实例!";
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";
chmod +x test.sh
./test.sh 1 2 3

输出

Shell 传递参数实例!
执行的文件名:test.sh
第一个参数为:1
第二个参数为:2
第三个参数为:3

其他处理参数的特殊字符:

参数处理说明
$#传递到脚本的参数个数
$*以一个单字符串显示所有向脚本传递的参数。
如"$*“用「”」括起来的情况、以"$1 $2 …$n"的形式输出所有参数。
$$脚本运行的当前进程ID号
$!后台运行的最后一个进程的ID号
$@与$*相同,但是使用时加引号,并在引号中返回每个参数。
如"$@“用「”」括起来的情况、以"$1" “$2” … “$n” 的形式输出所有参数。
$-显示Shell使用的当前选项,与set命令功能相同。
$?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

运算

[]符号

[ ] test命令的另一种形式。 具体看上面的test命令

注意事项:

  1. 你必须在左括号的右侧和右括号的左侧各加一个空格,否则会报错。

  2. test命令使用标准的数学比较符号来表示字符串的比较,而用文本符号来表示数值的比较。很多人会记反了。使用反了,shell可能得不到正确的结果。

  3. 大于符号或小于符号必须要转义,否则会被理解成重定向。

(())[[]] 符号

它们分别是[ ]的针对数学比较表达式和字符串表达式的加强版。
其中(( )),不需要再将表达式里面的大小于符号转义,除了可以使用标准的数学运算符外,还增加了以下符号:

符号描述
id++id--自增后、自减后
++id、`–id自增前、自减前
-+一元正负号、加减法
!~逻辑否定和位否定
**指数
*/``、%`乘法、除法、余数
<<>>左右位移
<=>=< >比较
==!=相等和不相等
&按位与
^按位异或
\|按位或
&&逻辑与
`|`
expr ? expr : expr条件运算符
=*=/=%=+=-=<<=>>=&=^=\|=赋值运算符
expr1 , expr2

注意:

并不是所有shell都支持[[]]和(())的。(Alpine不支持)

条件语句

语法:

if test-commands; then
consequent-commands;
[elif more-test-commands; then
more-consequents;]
[else alternate-consequents;]
fi

if else语句

if condition
then
    command1 
    command2
    ...
    commandN 
fi

可以写成一行(下面类似)

if [ $(ps -ef | grep -c "ssh") -gt 1 ]; then echo "true"; fi

if elseif else 语句

if condition
then
    command1 
    command2
    ...
    commandN
else
    command
fi

if else语句经常与test命令结合使用

num1=$[2*3]
num2=$[1+5]
if test $[num1] -eq $[num2]
then
    echo '两个数字相等!'
else
    echo '两个数字不相等!'
fi

循环

for 循环

格式1(遍历):
for name [ [in [words …] ] ; ] do cmmands; done
#or
for (( expr1 ; expr2 ; expr3 )) ; do commands ; done

样例:

arr=( a b c d e )
for name in ${arr[@]}; do printf $name; done
echo
for name in 1 2 3 4 5; do printf $name; done
echo
for (( a=1 ; a<10; a++)) ; do printf $a ; done

输出:

abcde
12345
0123456789

while循环

while test-commands; do consequent-commands; done

用例:

int=1
while(( $int<=5 )) do printf $int; let "int++" done 

输出:

12345

until循环

until test-commands; do consequent-commands; done

case 语句 (Switch语句)

语法:

case word in
[ [(] pattern [| pattern]) command-list ;;]esac

样例:

echo -n "Enter the name of an animal: "
read ANIMAL
echo -n "The $ANIMAL has "
case $ANIMAL in
horse | dog | cat) echo -n "four";;
man | kangaroo ) echo -n "two";;
*) echo -n "an unknown number of";;
esac
echo " legs."

输出:

Enter the name of an animal: dog
The dog has four legs.

breakcontinue

continue跳出当前循环,break结束整个循环


函数

linux shell 可以用户定义函数,然后在shell脚本中可以随便调用。
函数在当前 Shell 上下文中执行;

函数使用以下语法声明:

[ function ] funname [()]
{
 action;
 [return int;]
}

说明:

  • 1、可以带function fun() 定义,也可以直接fun() 定义,不带任何参数。
  • 2、参数返回,可以显示加:return 返回,如果不加,将以最后一条命令运行结果,作为返回值。 return后跟数值n(0-255)
  • 3、函数返回值在调用该函数后通过 $? 来获得。

案例:

#!/bin/bash
funWithReturn(){
    echo "这个函数会对输入的两个数字进行相加运算..."
    echo "输入第一个数字: "
    read aNum
    echo "输入第二个数字: "
    read anotherNum
    echo "两个数字分别为 $aNum$anotherNum !"
    return $(($aNum+$anotherNum))
}
funWithReturn
echo "输入的两个数字之和为 $? !"

输出:

这个函数会对输入的两个数字进行相加运算...
输入第一个数字: 
1
输入第二个数字: 
2
两个数字分别为 12 !
输入的两个数字之和为 3 !

函数返回值在调用该函数后通过 $? 来获得。

注意:所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。


函数参数

在Shell中,调用函数时可以向其传递参数。在函数体内部,通过 $n 的形式来获取参数的值,例如,$1表示第一个参数,$2表示第二个参数…

带参数的函数示例:

#!/bin/bash
funWithParam(){
    echo "第一个参数为 $1 !"
    echo "第二个参数为 $2 !"
    echo "第十个参数为 $10 !"
    echo "第十个参数为 ${10} !"
    echo "第十一个参数为 ${11} !"
    echo "参数总数有 $# 个!"
    echo "作为一个字符串输出所有参数 $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73

输出:

第一个参数为 1 !
第二个参数为 2 !
第十个参数为 10 !
第十个参数为 34 !
第十一个参数为 73 !
参数总数有 11!
作为一个字符串输出所有参数 1 2 3 4 5 6 7 8 9 34 73 !

注意,$10 不能获取第十个参数,获取第十个参数需要${10}。当n>=10时,需要使用${n}来获取参数。

另外,还有几个特殊字符用来处理参数:

参数处理说明
$#传递到脚本的参数个数
$*以一个单字符串显示所有向脚本传递的参数
$$脚本运行的当前进程ID号
$!后台运行的最后一个进程的ID号
$@与$*相同,但是使用时加引号,并在引号中返回每个参数。
$-显示Shell使用的当前选项,与set命令功能相同。
$?显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误。

输入/输出重定向

大多数 UNIX 系统命令从你的终端接受输入并将所产生的输出发送回​​到您的终端。一个命令通常从一个叫标准输入的地方读取输入,默认情况下,这恰好是你的终端。同样,一个命令通常将其输出写入到标准输出,默认情况下,这也是你的终端。

重定向命令列表如下:

命令说明
command > file将输出重定向到 file。
command < file将输入重定向到 file。
command >> file将输出以追加的方式重定向到 file。
n > file将文件描述符为 n 的文件重定向到 file。
n >> file将文件描述符为 n 的文件以追加的方式重定向到 file。
n >& m将输出文件 m 和 n 合并。
n <& m将输入文件 m 和 n 合并。
<< tag将开始标记 tag 和结束标记 tag 之间的内容作为输入。

需要注意的是文件描述符 0 通常是标准输入(STDIN),1 是标准输出(STDOUT),2 是标准错误输出(STDERR)。

实例:

echo "HelloWorld" > file
cat file

输出:

HelloWorld

Here Document

Here Document 是 Shell 中的一种特殊的重定向方式,用来将输入重定向到一个交互式 Shell 脚本或程序。

它的基本的形式如下:

[n]<<[-]delimiter
    here-document
delimiter

它的作用是将两个 delimiter(也可以换成其他) 之间的内容(document) 作为输入传递给 command。

注意

  • 结尾的delimiter 一定要顶格写,前面不能有任何字符,后面也不能有任何字符,包括空格和 tab 缩进。
  • 开始的delimiter前后的空格会被忽略掉。

实例:

$ wc -l << EOF
    Hello
    My Friend
EOF
3          # 输出结果为 3 行

/dev/null 文件

/dev/null 是一个特殊的文件,写入到它的内容都会被丢弃;如果尝试从该文件读取内容,那么什么也读不到。但是 /dev/null 文件非常有用,将命令的输出重定向到它,会起到"禁止输出"的效果。

如果希望执行某个命令,但又不希望在屏幕上显示输出结果,那么可以将输出重定向到 /dev/null

$ command > /dev/null

协程和并行

coproc&指令

coproc [NAME] command [redirections]

coproc 是一个 shell 命令,也是一个保留字。协同进程在子 shell 中异步执行,就好像命
令已经被“ &”控制操作符终止一样,在执行的 shell 和协同进程之间建立了一个双向管道。

&指令

command &

&指令类似于coproc

parallel 命令

顾名思义,可以用来并行地构建和运行命令。

当文件数量太大而不能通过一次 mv 调用来处理时,你可以使用并行来从工作目录中移动文件

printf '%s\n' * | parallel mv {} destdir

管道

[time [-p]] [!] command1 [ | or |& command2 ]

管道比较重要,是经常需要用到的一个知识。

管道是由一个控制运算符|| &分隔的一个或多个命令的序列, 通俗的讲就是一个命令的输出输入到另一个指令,类似于水流经一个管道处理完后流向另一个管道。

管道中每个命令的输出通过管道连接到下一个命令的输入。也就是说,每个命令都读取前一个命
令的输出。此连接在命令指定的任何重定向之前执行。

time保留字会导致在管道结束时为其打印计时统计信息。

$ ls /|grep bin
bin
sbin

其他

()分组命令

在圆括号之间放置一个命令列表会创建一个子 shell 环境(请参阅命令执行环境) ,列表中的
每个命令都会在该子 shell 中执行。因为列表是在子 shell 中执行的,所以变量赋值在子 shell
完成后不会继续有效。

( b=10; ); ( echo $b )

该命令将不会输出

参考

  1. Shell 教程_w3cschool

  2. Bash Reference Manual

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值