音无结弦之时,天使跃动之心。立于浮华之世,奏响天籁之音。.|

次林梦叶

园龄:3年3个月粉丝:22关注:3

Linux--Shell

image

早上好,未来

Dreamin' Her - 僕は、彼女の夢を見る


Shell 创建与使用新命令

步骤:

  • 将脚本保存到文件中
  • 赋予文件执行权 chmod +x file
  • 将文件放到$PATH目录下(一般都是保存到\home\用户名\bin下)

当然我们也可以直接如下

image

这种方法每一次都要写./ 不同方便
上述步骤方法可以让我们直接使用 nu 这种写

书写Shell脚本

image

#! 的作用非常大,他可以帮助我们在执行脚本的时候不用每次都写解释执行脚本的程序
如awk -f 可以省略,在我们写了#!/usr/bin/awk -f之后


命令执行的整个流程

image

参数处理

当我们写Shell脚本的时候如何处理参数呢?

$*

比如我们就写了个脚本MYLS,其中的内容就是ls -l
但是ls -l 后可以接多个参数 file1,file2,....
当我们也想我们的脚本实现MYLS file1 file2...咋办?

可以脚本中写 ls -l * 可以代表全部参数(不包括0@作用基本与$*相同

$0

$0在shell脚本中代表shell文件名
需要注意的是这个文件名是全名称的,即:

image

image

如果就只想要活动文件名,可以试试basename命令
image
image



我们还可以使用1,2....910{10} 表示第10个参数

$# 表示总的参数个数

shift[n] 命令可以对参数进行左移,一般是写在脚本中,n是参数

echo $#
echo $1
shift 2
echo $1

上述操作后第一次打印的是参数个数,然后打印第一个参数,然后将全部参数左移2个,然后打印第一个参数(这个时候第一个参数其实是原理的第三个参数)


命令替换

思考一下如下问题:

我们如何将一个命令的运行结果作为参数呢?

image

即可以使用`` 的方式,但是在``中再希望使用`` 需要加上
更好的方法是使用$()


变量

我们在shell中可以直接定义变量

image

需要注意的是 x=100 之间是不能有任何空格的,否则100 会被当做参数,x会被当做命令来处理,结果就是
image

还需要注意的是我们在shell定义的变量默认保存的是string值

y=6/3
echo $y

结果不是2,而是6/3

作用域

当然,我们的变量也是有作用域的

一般在父Shell下定义的变量是不能够在子shell中使用到的

解决方法 export x (x为变量)


一般我们是在子Shell中改动父shell中定义的变量后,返回父Shell是不影响父Shell的 (比如我们写脚本对父Shell进行更改是无用的,因为脚本其实是原来Shell的子Shell)

解决方法: 我们可以使用". 脚本" 的方法在父shell中执行这个脚本
(source 脚本效果也是一样的)

这个其实是有经常用到的地方,如:我们希望我们改过配置文件后,不用重启操作系统就可以立刻更新

image


删除变量、声明变量和特殊变量

x=100
unset x

被声明为只读的变量无法删除(declare -r x)

help declare 查看declare 声明变量的帮助

image

我们知道我们的变量默认是string类型的,我们可以

declare -i x
x=6/3

这次结果为2

image


进制

image

image


特殊变量

image


变量替换、条件变量替换

思考一下如下要求:

a=teach
# 我想要打印出I'm a teacher如何办到?

这样吗?echo "I'm a $aer"
不,错误的,他会把$aer当成变量

答案是echo "I'm a ${a}er"

需要注意的是这里的双引号不能改为单引号,因为单引号是强引用,会将其中全部的东西当做字符串

()是命令替换 注意区别

${!y} !会递归地将最初始的值返回出来:
image


条件变量替换

image

比如:

image

这个命令的作用是如果1shelllsl1
否则ls -l . 这个.表示当前目录


截取变量(有点substr使用的感觉?)

image


varname="game.tar.gz"
echo ${varname%.*}
echo ${varname%%.*}

image

需要注意的是这个这种是文件通配符的中的含义
varname%.* 表示去除varname中与.* 最短匹配上的内容


echo ${#varname} //输出变量长度
echo ${varname:0:3} //取变量varname从第0个字符开始,3个字符、
echo ${varname::3} //省略开始字符默认从0开始
echo ${varname:-1:} //-1 表示倒数第一个字符

字符串匹配

value="This is a test and the test is hard"
echo ${value/test/Test} //将匹配到的test单词替换为Test,匹配到后只能替换一次
echo ${value//test/Test} //全局替换
echo ${value/#test/Test} //与最左边单词进行匹配,匹配上了则替换,否则不替换
ehco ${value/%test/Test} //与最右边单词进行匹配,匹配上了则替换,否则不替换
echo ${value//test/} //表示删除test

shell中字符串匹配功能还是比正则弱不少的


数值计算

只能进行整数运算

  • 内部命令: let 和 declare
declare -i x

让变量为int型没啥好说的

help let | less

image

image



  • 外部命令
man expr

image

image

这里其实可以看到内部命令与外部命令的区别
内部命令如let 可以与shell本身的一些内部实现进行一些交互
比如可以直接使用let a=1 b=2 c=a+b 这样的
但是外部命令 expr 1 + 2 这里因为是将1,+,2当做参数,所以他们之间必须要有空格
外部命令是接受参数进行执行



  • 算数扩展

image

$[] 效果好像也是一样的


可进行小数运算的bc

man bc

image

注释

单行注释 #

打印

printf "%d %s %d\n" $a $b $c

条件语句

echo $?

通过上述命令我们可以知道上一条写的条件语句是真还是假

真为0,假为1

test "$str" = "a b"
# 上述 test 是 用来判断变量$str与字符串“a b”是否相同的
# 为何 $str 要用“”包住?因为$str其实相当于c中的# ,其处理的方式是宏替换
# 即上述语句其实写的是 test a b = "a b"
# 这样写肯定不行
[ "str" = "a b" ]
# [] 与 test有同样效果

这下不得不说说 [] 了

help test 查看帮助文档

image

image

image

可以看到[[]] 中是可以支持 文件扩展符的
image
其他一些可以在help test中看到



: 永远返回0

image


总结一下

我们如果想要像c++ 中有数值计算那么就用 $(()) 进行计算

如果算完后想要判断,那么用[ xxx -eq ] 这样的方式进行判断

数值千万不要用 == 等进行比较,判断! == 是判断字符串用的


流程控制

if [ xxx ]; then
xxx
elif [ xxx ]; then
xxx
else
xxx
fi
case xxx in
xxx) dosomething;;
xxx) dosomething;;
...
esac
while [ xxx ]; do
xxx
xxx
done
for i in `seq 10`; do
echo $i
done
还有用法是:
for ((i=1;i<=10;i++)); do
echo $i
done
# 上述这条shell命令会打印出1~10的数值

image
image
image

同样也有 break 和 continue

用 \ 进行代码换行

image

关于for 循环的参数问题

for i in * ; do
echo $i
done
for i in $* ; do
echo $i
done
  • 表示当前执行文件夹的全部文件,这里可以支持文件扩展符
    $* 就表示全部的参数
for i in "$@"; do
echo $i
done

上述这种写法将全部的参数当做字符串,这样做可以处理参数是字符串时不会出现奇奇怪怪的问题

参数的分隔依据

image

image

可以看到,他这里读参应该是根据 空格 \n \t 进行分隔

其实他其实是参照环境变量 $IFS 当做分隔符的

image

当然我们可以改动:

image

我们在原来的$IFS上加上了 ,与 :当做分隔符


用户交互

read

read 是相当于c++ 中的 cin

通过 help read 查看帮助信息

image

-p 可以 在输入时添加提示
-s 是不显示输入的内容

image

select

image


函数

函数的书写方式与调用

image

函数中参数的使用

在上面函数的调用方法中我们可以看到 我们就像普通调用命令一样,调用函数,然后参数追加到函数后面

在函数中使用参数依然是用1,@等

函数中全局变量和局部变量

image

可以看到我们在函数中定义的变量也是全局变量

为了定义局部变量需要再变量前加上 local 字段
image


注意! 使用 管道时 变量的范围的问题

image

为何a不等于“5678”呢?

在 man bash中的 pipelines下

image

即在管道中执行的命令都是开了一个子shell进行运行的,其中的变量并不是我在父shell中定义的

再如:

image

这个n肯定我们执行后是0,咋解决呢?

不用管道了!

image

这用法真新奇

函数的返回值

我们可以通过 $? 来获取 函数的用return返回的 返回值
但是这是有范围的

image

所以一般我们通过接受函数的输出值来得到函数的返回值

我们可以通过result=$(functionName)
这时 result就得到了函数中输出的值


image

我们来分析一下上述程序

首先,我们输出“Enter a value:\c”
这是提示

echo -e echo加上-e选择后可以解析一下特殊字符

这里的\c是为了echo 不自动换行

然后我们将标准输定向到标准错误输出,即为了防止这个提示语句在我们调用函数后被接受到

echo 后是我们要输出的结果
result用于接收


函数库

image

需要注意下这里 error $msg 的写法,这种写法可以让字体变色,即更像警告一样了

数组

数组的基础使用

  • 定义数组
declare -a arrName
arrName=("xx" "xx" "xx"...)
declare -a #可查看定义的数组
  • 访问数组
# 数组下标从0开始
echo ${arrName[3]}
# 打印数组全部内容
echo ${arrName[*]}
echo ${arrName[@]}
# 打印数组长度
echo ${#arrName[*]}
# 从数组下标3开始,取3个值
echo ${a[*]:3:3}
  • 数组的变量替换

image

数值传值

  • 将read内容保存到数组中
xxx | while IFS=":" read -a arrName ; do
xxx
done

image

  • 将数值作为参数传递给函数

即 我们首先需要将数组中的内容通过${arrName[@]}展开

result=(functionName{arrName[@]})

  • 函数返回数组

function functionName(){
xxx
echo ${arrName[@]}
}

我们通过(functionName)()result=((functionName))

将数据与脚本放在一起 -- Here文档

xxx<<HereName
xxx
xxx
...
数据
xxx
HereName
HereName是随意的

image


here文档中忽略特殊字符问题

在here文档中
$(),${}等依旧是有效的,可以进行替换操作
如果不想要这种特殊替换的话可以在前面加上\
即\$()
如果想要全局禁止特殊替换可以在HereName前加上\
即 xxx <<\HereName
...
HereName

image
image

here文档中忽视数据存放时Tab格式问题

image

如果没有这个- 那么我们输出到$1的内容将会保留我们写在HTMLSKEL之间的格式

利用here文档实现shell中多行注释

: <<Explanatory_note
xxx
xxx
...
xxx
Explanatory_note

here文档实际运用

实现将多个文件打包并解包的功能

实现这个功能的脚本为bundle

image

bundel file1 file2 >> allfile 这条命令是将file1,file2打包进allfile
sh allfile 这条命令可以将allfile解包,回复fil1,file2

其实实现的逻辑很简单,通过$* 遍历每一个参数文件,然后利用cat iecho"cat>i <<'End of i"echo"Endofi"形成here文档的形式,将打印出来的文件内容包裹起来,而且将脚本写入allfile中

这个时候allfile其实就是这个包含了数据的脚本,然后通过执行这个脚本可恢复源文件

exec

作用一:执行新进程并用新进程替代当前进程

uname -r
exec date
echo 'exec failed!'

exec 执行后当前shell被替换内容被替换,exec下面的全部命令也就执行不了了

作用二: 重定向

image

打开sh.out写,并给它分配文件描述符4
exec 4>sh.out

把输出文件描述符4复制到1,即文件描述符1指向与文件描述符4一样的地方
exec 1>&4

关闭文件描述符4
exec 4>&-

将标准输出重定向到/dev/tty(即当前的终端)
exec >/dev/tty

实践

我现在想要从file1和file2文件逐行读入

下面这么写对吗?

image

不对! 这样写read line1<$file1相当于不断打开file1进行读,这样永远会只读第一行


image

我们这里的做法是将file1打开,并赋予文件描述符3

然后将read的标准输入指向文件描述符3,这样就避免了重复打开file1的问题

进程替换

image

一个好博客:解释了管道与进程替换的区别

我们来理解一下:

image

说明 <()操作其实取的是执行()中命令 对应的/dev/fn/n文件

image

我们首先打印出了 ls 对应的/dev/fn/n文件,然后ls执行了,所以就有上图的结果
再如:
image

image

我们打印出"Hello" 然后通过 > 给文件/dev/fn/n ,然后cat /dev/fn/n

管道与进程替换的区别

在上述博客中提到了:

image

命名管道

命名管道是个文件,普通管道是个程序

mkfifo fifoName

ls -l fifo

最前面的 p 表示其是一个文件管道

image

一些内部命令

命令分为内部命令和外部命令,内部命令本身就是Shell,执行内部命令是在当前Shell执行

然后执行外部命令需要,需要创建子Shell中执行

set

  • set 命令主要用来设置shell,在编写shell脚本时,使用 set 命令能设置shell的执行方式,根据需求不同,采用的参数设置也不同。set 命令也用来显示系统中已存在的shell变量以及设置新的shell变量。
    参考博客

  • set 可以重置shell中的参数
    image

这个用法可以扩展出很多小技巧
image
通过set改变参数,再通过$#得到全部参数个数


eval

参考博客

eval会对后面的命令进行两遍扫描,

如果第一遍扫描后,命令是个普通命令,则执行此命令;
如果命令中含有变量的间接引用,则保证间接引用的语义。也就是说,eval命令将会首先扫描命令行进行所有的置换,然后再执行该命令。

因此,eval命令适用于那些一次扫描无法实现其功能的变量。

  • eval 执行以下两个步骤:
    • 第一步,执行变量替换,类似与C语言的宏替代;
    • 第二步,执行替换后的命令串。

image

不使用eval,执行出错:
image

反之:
image


信号

image

man 7 signal
image
可以查看更详细信息

trap 可以用于捕获信号并进行处理

image

当Shell是因为信号而停止时,$?的值为128+信号的值
image

image

然后为了不让每一次按ctrl+c都出现 I get 130,我们需要使用
trap - 2

结合函数使用

image

我们可以看到trap 后面的“”中调用了函数


Shell选项处理

简单选项处理

image

shift 默认执行的是 shift 1
表示将参数整体向左移一位,即可以得到将原来的21 效果
具体博客<---

我们这里利用shift不断让参数到达$1这个位置

  • 利用 -- 分离选项和参数
    image

-- 之后就是参数了

合并选项处理

如 : -ac

上面如:

case "$1" in
-a)
-b)
-c)
*)

我们是有对a,c选择的处理

-a -c 分开了写就行

但是-ac合起来写,我们上面的简单处理就会报错

getopt

getopt ab:cd -a -b test1 -cd test2 test3
ab:cd
: 表示b后面必须要有参数

然后我们的 getopt 会将合并的参数给自动拆开了,而且参数后没有写":" 的会被认为没有参数,上述代码执行结果为:
image

  • 需要注意的是 getopt对“”中内容的处理
    image
    可以看到其中""中的内容并没有被当做一个参数,而是分开来了

有个问题就是选项后只接受一个参数,多了参数会有奇怪的问题

image

处理“”中参数问题的getopts

image

image

在选项前面加“:”是为了忽略错误,在后面加“:”是为了说明其后有参数
上述代码中opt保存了选项后的参数

执行这个代码:

有与getopt一样的问题:

image

如果是选项后出现多个参数会出现奇怪的问题


正常效果:

image

image

如红框这么做,可以得到非选项的参数
[](())效果一样

终端控制

颜色设置

我们可以通过在输出内容前写 ESC[ 来控制输出颜色

\033[
\e[
也代表ESC[
ESC[arg1;arg2;...m
m为结束

利用 man console_codes 来查看书写和颜色编号

image

image

echo -e "\e[4mhello world\e[0m"
前者\e是为了表示hello world是用特殊书写
后者\e是为了恢复常态
-e 是为了忽视特殊字符

终端控制 tput

man tput :

image

image


后面的参数我们可以通过 man 5 terminfo 查看:

image

我们经常有些会连用,比如:

tput sc # 先保存光标的位置
xxx
tput cpu r c #将光标移动到指定位置进行一些操作
xxx
tput rc #恢复光标的位置
tput setf 1
tput setb 2
xxx
tput sgr0 #将上述设置的一切给取消,下面接着使用默认值

运用

结合命令替换使用

image

这里有个惊人的使用方法,我们知道 `` 是用来命令替换的 , 这里将${}展开命令替换操作
然后可以通过这个方式让这个输出的hello有特殊样式

结合Here文档使用

image

效果如下:
image

制作选择菜单

image
image

效果如下:
image


调试

shell的-v和-x选项

image

比如我下面的一段测试脚本:
image

不开启调试:
image

开启-v调试:
image

可以看到-v只是将原始的句子打印出来而已
image
对应更复杂的有循环语句,他也只是打印出来原始句子


开启-x调试:
image

可以看到+x会把过程中的变量等信息展示出来
image
对应更复杂有循环的语句会逐步进入展示

在脚本中开启和关闭调试

在脚本中通过 -x/-v 开启调试

然后下面的代码都是开启调试的状态

再通过+x/+v 关闭调试状态

image

利用trap和伪信号进行调试

利用trap命令进行调试

伪信号:

由shell而非内核产生的信号

EXIT/0 从函数或脚本中退出
RETURN 调用函数或使用“.”命令执行其他脚本之后
ERR 从某条命令返回非0状态(不成功)时
DEBUG 脚本中每条命令执行之前

image

可以用于当我们要退出Shell脚本的时候,可以利用trap "" EXIT
察觉到脚本要退出了,在“”中可以执行一些清除这个脚本中产生的中间文件的操作

image

set -o functrace 记得写上

利用debug钩子进行调试(即就是写debug函数,并通过变量的值来灵活调试)

image

可以看到,我们debug()函数中通过DEBUG变量来判断是否进行debug
在调用这里脚本时,可先对DEBUG赋值来决定是否需要调试


通过()和 {} 设置在当前Shell执行还是在子Shell下执行

image

可以看到上述案例,我们将cd 这条命令在子shell下运行,这样我们执行完后的Shell并不会到 cd的目录下

image

我们在子Shell下运行的并不会对本Shell中变量造成影响

但是当我们用{}时:

image

注意{}中以 “;”进行分隔,而且最后的语句也要用;结尾
image
还可以有上面的写法


实现Shell脚本中将参数给awk

image

这个程序通过管道,将who的输出连接到了shell脚本testawk的输入,因为awk就是从标准输入中读数据,所以awk能够接受到数据
注意我们这里的同样可以给testawk 传参数
然后1testawk1被替换后(假设传入的为3),那么$3就是awk要处理的第3个字段

'{ print 1为一部分; ' }'为一部分
通过这三部分组成了awk要执行的程序

综合大作业:实现就业管理系统

先思考下下面这个页面咋做?

image

# 首页面,对system进行讲解:
40 WelcomeIndex(){
41
42 sr=5
43
44 tput clear
45 center $sr "Welcome to Employment System"
46 titleFont "Welcome yo Employment System!"
47 sr=$[sr+2]
48
49
50 center $sr "The author is ${author}"
51 echo "The author is ${author}"
52 sr=$[sr+2]
53
54 center $sr "Now the system version is ${version}"
55 echo "Now the system version is ${version}"
56 sr=$[sr+2]
57
58 tput cup $sr 5
59 echo "Description:"
60 sr=$[sr+2]
61
62 tput cup $sr 7
63 echo "The main function of this employment system is to manage a large number of informations about employment situation of college students."
64 sr=$[sr+3]
65
66
67 tput cup $sr 7
68 echo "You can use this system to accomplish a series of functions included Add,Delete,Alter,Find."
69 sr=$[sr+3]
70
71 tput cup $sr 7
72 echo "Good Luck!"
73 sr=$[sr+2]
74
75 center $sr "Now you can press Any Key to continue!"
76 titleFont "Now you can press Any Key to continue!"
77
78 tput sgr0
79
80 read -n1 key
81 }

一些初始时需要设定的函数和参数

1 #!/bin/bash
2
3 # 得到当前窗口的大小
4 pwidth=$(tput cols)
5 phight=$(tput lines)
6 author="次林梦叶"
7 version="0.1"
8
9 #程序在执行过程中会产生中间文件,需要在程序退出的时候清除
10
11 #trap 'rm -rf ./tmp &>/dev/null; tput sgr0;' EXIT
12
13
14
15 # 输入行数,自动将文字打印到屏幕中间
16 center(){
17 tput cup $1 $(((pwidth-${#2})/2))
18 }
19
20 # 输入时用的格式,比center更加往左边一点
21 leftCenter(){
22 tput cup $1 $(((pwidth-${#2})/4))
23 }
24
25 # 专门用来打印标题文字格式
26 titleFont(){
27 tput bold
28 echo -e '\033[42m'$1'\033[49m'
29 tput sgr0
30 }
31 # 专门用来打印选择文字格式
32 selectFont(){
33 echo -e '\033[4;33m'$1'\033[49m'
34 }
35 # 专门用来打印错误文字格式
36 errorFont(){
37 echo -e '\033[41m'$1'\033[49m'
38 }

我自认为写的不错的一些方法

291 #修改的基本想法是,通过cut得到id,然后与employment.db中的每一个id进行比较
292 #不同的就放到临时文件中,因为这说明这条信息是不用更改的,最后将临时文件中的内容代替employment.db
293 flag="fail"
294 touch ./tmp/altTemp
295 while read line ; do
296 field1=$( echo $line | cut -d'%' -f1 )
297
298 if [ "$field1" != "$key" ] ; then
299 echo $line >> ./tmp/altTemp
300 else
301 flag="success"
302 alterLine=$line
303 fi
304 done < ./employment.db
305
306 cat ./tmp/altTemp > ./employment.db
307 rm ./tmp/altTemp
308
309 if [ $flag == "fail" ]; then
310 center $sr "Error! Can't find field what you enter!"
311 errorFont "Error! Can't find field what you enter!"
312 sr=$[sr+2]

本文作者:次林梦叶

本文链接:https://www.cnblogs.com/cilinmengye/p/17788970.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   次林梦叶  阅读(28)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起