使用Shell笔记总结

一、基本数据定义

1.1 变量

1、定义变量不加$符号,使用变量要用$等号两边不能直接接空格符;通常大写字符为系统默认变量,自行设定变量可以使用小写字符。

2、双引号内的特殊字符如 $ 等,可以保有其符号代表的特性,即可以有变量、转移字符;单引号内的特殊字符则只会原样输出。

NAME=yanzu;your_name="wu$NAME";echo $your_name         # wuyanzu
NAME=tingfeng;your_name='xie$NAME';echo $your_name     # xie$NAME

3、在一串指令的执行中,还需要藉由其他额外的指令所提供的信息时,可以使用反单引号 `指令`$(指令)

# uname -r 作为系统命令,并执行其内容
version=$(uname -r);echo $version
version=`uname -r`;echo $version

4、欲为扩增变量内容时,则可用 "$变量名称"${变量} 累加内容。

PATH="$PATH":/home/bin      # 字符串就是这样拼接的
PATH=${PATH}:/home/bin

5、若该变量需要在其他子程序执行,则需要以 export 来使变量变成环境变量。

export PATH
env 			# 可查看环境变量

6、使用readonly可以将变量定义为只读变量,只读变量就是一个常量。unset 命令可以删除变量,变量被删除后不能再次使用,不能删除只读变量。

name="wuyanzu"
readonly name	# 指定name为只读变量
age=13
unset age 		# 后面不能再使用age

7、预设变量,开始执行Script脚本时就会设定,且不能被修改。

$#	#传递到脚本的参数的数量
$*	#表示所有位置参数的内容,即以一个字符串显示所有向脚本传递的参数;$* 以"$1 $2 … $n"的形式输出所有参数
$@	#与$*相同,$@以"$1 $2 … $n"的形式输出所有参数
$$	#当前进程的进程号PID
$?	#上个执行指令的回传值;用于检查上一条指令执行是否正确(linux执行返回的状态值:0表示没有错误,其他任何值表明有错误)
$!	#后台运行的最后一个进程的进程号pid

8、如果不特别指明,每一个变量的值都是字符串,无论你给变量赋值时有没有使用引号,值都会以字符串的形式存储。

1.2 字符串

字符串可以用单引号,也可以用双引号,也可以不用引号。 字符串常用操作:拼接、获取长度、提取/替换子串、查找字符。

#!/bin/bash
your_name="wuyanzu"

# 单引号拼接
echo 'hello, '$your_name        #hello, wuyanzu
echo 'hello, '${your_name}      #hello, wuyanzu

# 双引号拼接,除单引号方式外加以下方式
echo "hello, $your_name"        #hello, wuyanzu
echo "hello, ${your_name}"      #hello, wuyanzu

# 获取长度
echo ${#your_name}              #7
expr length $your_name

# 查找字符位置
expr index $your_name u         #查找u第一次出现的位置,没有就是0,输出:2
expr index $your_name yu        #查找y和u第一次出现的位置,谁先出现算谁,输出:2

# 提取子串
echo ${your_name:2}     #从索引2开始,至结尾。输出:yanzu
echo ${your_name:2:3}   #从索引2开始,取3个字符。输出:yan
expr substr $your_name 2 3      #从第二个字符开始,取3个。输出:uya

# 匹配删除子串
echo ${your_name#"wu"}  #从头开始,匹配最短的wu,将其删除后。输出:yanzu
echo ${your_name##"wu"} #从头开始,匹配最长的wu,将其删除后。输出:yanzu
echo ${your_name%"wu"}  #从尾开始,匹配最短的yanzu,将其删除后。输出:wu
echo ${your_name%%"wu"} #从头开始,匹配最长的yanzu,将其删除后。输出:wu

# 替换子串
echo ${your_name/u/o}   #使用u替换从头开始第一个匹配的o,输出:woyanzu
echo ${your_name//u/o}  #使用u替换匹配到的所有o,输出:woyanzo

1.3 数组

Bash Shell 只支持一维数组(不支持多维数组),初始化时不需要定义数组大小,数组元素的下标由 0 开始。

Shell 数组用括号来表示,元素用"空格"符号分割开。

#!/bin/bash

# 定义一个数组
your_name=(w u yan "zu")

your_name[4]="!"

# 获取指定元素
echo ${your_name[2]}    #获取第2个元素,输出:yan

# 获取数组长度
echo ${#your_name[*]}   # 5
echo ${#your_name[*]}

# 获取所有元素
echo ${your_name[*]}    # w u yan zu !
echo ${your_name[@]}

# 关联数组
declare -A name=(["my_name"]="wuyanzu" ["your_name"]="pengyuyan")
name["his_name"]="panchangjiang"

echo ${name["my_name"]} # wuyanzu
echo ${#name[*]}        # 3
echo ${name[*]}         # pengyuyan wuyanzu panchangjiang

二、基本运算符

原生bash不支持简单的数学运算,而是把运算符两边的数据(数值或者变量)当做字符串,把运算符本身也当做字符串,最终的结果是由一个运算表达式形成一个新的字符串。

但是可以通过其他命令来实现,例如和 (())$[]letexprawk

#a=a+1
a=$((a+1))
a=$[a+1]
let a=a+1
a=$(expr $a + 1)
a=`expr $a + 1`

除了使用let运算之外,其他运算符有以下两个要注意的点:

  • 对于所有的运算符,表达式和运算符之间要有空格,例如2+2是不对的,必须写成2 + 2
  • 条件表达式要放在方括号之间,并且要有空格,例如: [$a == $b]是错误的,必须写成 [ $a == $b ]

let 命令对格式要求要比 expr 命令宽松,变量计算中不需要加上 $ 来表示变量,且表达式和运算符之间不要空格也ok。

2.1 算数、字符串运算符

乘号*前边必须加反斜杠\才能实现乘法运算,如果运算表达式中使用()运算符,也要加反斜杠\,如a \* \( b + c \)

# 算数运算符
let c=a+b		# c=a+b
let 'c= a +b'	# c=a+b
let a++			# a=a+1
let a+=10		# a=a+10
#下列运算等价于:$(expr $a + $b)
`expr $a + $b`	# a+b
`expr $a - $b`	# a-b
`expr $a \* $b`	# a*b
`expr $b / $a`	# a/b
`expr $b % $a`	# a%b

a=`expr $a + $b`# 把运算结果赋给a
a=$b 			# 把变量b的值赋给a
[ $a == $b ]	# a是否等于b
[ $a != $b ]	# a是否等于b

# 字符串运算符
[ $a = $b ]     #检测两个字符串是否相等,相等返回 true
[ $a != $b ]    #检测两个字符串是否不相等,不相等返回 true
[ -z $a ]       #检测字符串长度是否为0,为0返回 true
[ -n "$a" ]     #检测字符串长度是否不为 0,不为 0 返回 true
[ $a ]          #检测字符串是否不为空,不为空返回 true

2.2 关系、布尔、逻辑运算符

关系运算符只支持数字,不支持字符串,除非字符串的值是数字。

# 关系运算符
[ $a -eq $b ] #	a == b
[ $a -ne $b ] #	a != b
[ $a -gt $b ] #	a > b
[ $a -lt $b ] #	a < b
[ $a -ge $b ] #	a >= b
[ $a -le $b ] #	a <= b

# 布尔运算符
[ $a -lt 20 -a $b -gt 100 ] # -a:与
[ $a -lt 20 -o $b -gt 100 ] # -o:或
[ ! false ]                 # !:非

# 逻辑运算符
[[ $a -lt 100 && $b -gt 100 ]]	# 逻辑AND
[[ $a -lt 100 || $b -gt 100 ]]	# 逻辑OR

2.3 文件测试运算符

文件测试运算符用于检测 Unix 文件的各种属性。

[ -b $file ]    # 检测文件是否是块设备文件,如果是,则返回 true
[ -c $file ]    # 检测文件是否是字符设备文件,如果是,则返回 true
[ -d $file ]    # 检测文件是否是目录,如果是,则返回 true
[ -f $file ]    # 检测文件是否是普通文件(既不是目录,也不是设备文件),如果是,则返回 true
[ -g $file ]    # 检测文件是否设置了 SGID 位,如果是,则返回 true
[ -k $file ]    # 检测文件是否设置了粘着位(Sticky Bit),如果是,则返回 true
[ -p $file ]    # 检测文件是否是有名管道,如果是,则返回 true
[ -u $file ]    # 检测文件是否设置了 SUID 位,如果是,则返回 true
[ -r $file ]    # 检测文件是否可读,如果是,则返回 true
[ -w $file ]    # 检测文件是否可写,如果是,则返回 true
[ -x $file ]    # 检测文件是否可执行,如果是,则返回 true
[ -s $file ]    # 检测文件是否为空(文件大小是否大于0),不为空返回 true
[ -e $file ]    # 检测文件(包括目录)是否存在,如果是,则返回 true

2.4 test 命令

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

具体的条件关系、布尔、逻辑、以及文件测试,可参考以上基本运算符。

cd /bin
if test -e ./notFile -o -e ./bash
then
    echo -e "至少有一个文件存在!\n"    # -e可输出转移字符
else
    echo -e "两个文件都不存在!\n"
fi

三、控制流与函数

3.1 条件语句

知道if...elif...else的写法,其他的就都会写了。

if condition1; then	# 如果条件表达式与then连着写,要使用分号‘;’分割
    command1
elif condition2 	# 这里分号‘;’可写可不写
then 
    command2
else
    commandN
fi

3.2 循环语句

在循环中也可以使用breakcontinue

for var in item1 item2 ... itemN; do command1; command2… done;

while condition
do
    command
done

until condition
do
    command
done

3.3 分支语句

case取值后面必须为单词 in,每一模式必须以右括号结束。

取值可以为变量或常数,匹配发现取值符合某一模式后,其间所有命令开始执行直至 ;;

read aNum
case $aNum in
    1)  echo '你选择了 1'
    ;;
    2)  echo '你选择了 2'
    ;;
esac

3.4 函数

定义函数function关键字可选,不带任何参数,所有函数在使用前必须定义。

函数的返回值只能是一个介于 0~255的值,在调用该函数后通过 $? 来获得,参考预设变量

# 函数定义,可以不用function
function demo(){
    echo "第一个参数为 $1"
    echo "第二个参数为 $2"
    echo "参数总数有 $# 个"
    echo "作为一个字符串输出所有参数 $*"
    return 8
}
# 函数调用
demo 1 2 3 4 5 6 7
echo "demo 返回值为 $?"

四、重导向与管线命令

4.1 数据流重导向(>、>>、<、<<)

标准输入(standard input)是命令的输入,默认指向键盘。数据流重导向可以将原本需要由键盘输入的数据,改由文件内容来取代。

  1. 标准输入 (stdin) :代码为 0 ,数据流重导向使用 <<<
  2. < :将原本需要由键盘输入的数据,改由文件内容来取代。
    # 用 stdin 取代键盘的输入以建立新文件的简单流程,相当于copy
    cat > catfile < ~/.bashrc
    
  3. << :代表的是结束的输入字符 的意思。
    # 用 stdin 取代键盘的输入以建立新文件的简单流程,相当于copy
    cat > catfile << "eof"
    > this is test
    > eof	# 输入这关键词,立刻就结束而不需要输入 [ctrl]+d
    cat catfile 
    this is test
    

标准输出(standard output)指的是指令执行所回传的正确的讯息,默认指向屏幕;标准错误输出(standard error output)可理解为指令执行失败后,所回传的错误讯息,默认指向屏幕。数据流重导向可以将输出分别传送到其他的文件或装置去。

  1. 标准输出 (stdout):代码为 1 ,数据流重导向使用 >>>1>1>>);
  2. 标准错误输出(stderr):代码为 2 ,数据流重导向使用 2>2>> (没空格)。
  3. > :以覆盖的方法将数据输出到指定的文件或装置上(文件原有内容会被覆盖);
  4. >>:以累加的方法将数据输出到指定的文件或装置上(在文件原有内容上输出);
    # 将 stdout 与 stderr 分存到不同的文件去
    find /home -name .bashrc > list_right 2>> list_error
    

空设备文件/dev/null可以理解为一个垃圾桶黑洞装置,它可以吃掉任何导向这个装置的信息。

# 将错误的数据丢弃,屏幕上显示正确的数据
find /home -name .bashrc 2> /dev/null

如果要将正确与错误数据通通写入同一个文件,这个时候就得要使用特殊的写法。两股数据同时写入一个文件,又没有使用特殊的语法, 此时两股数据可能会交叉写入该文件内,造成次序的错乱。

# 将指令的数据全部写入名为 log 的文件中
find /home -name .bashrc > log 2> log	<==错误
find /home -name .bashrc > log 2>&1		<==正确,比较常见的一种写法
find /home -name .bashrc 2> log 1>&2	<==正确
find /home -name .bashrc &> log			<==正确

【总结】为何要使用命令输出重导向呢?有以下几点:

  • 屏幕输出的信息很重要,而且我们需要将他存下来的时候;
  • 背景执行中的程序,不希望他干扰屏幕正常的输出结果时;
  • 一些系统的例行命令 (例如写在 /etc/crontab 中的文件) 的执行结果,希望他可以存下来时;
  • 一些执行命令的可能已知错误讯息时,想以『 2> /dev/null 』将他丢掉时;
  • 错误讯息与正确讯息需要分别输出时。

4.2 命令执行的判断依据(;、&&、||)

cmd1; cmd2
cmd1执行的结果是否正确不影响cmd2的执行,则cmd1执行完后就会立刻接着执行后面的cmd2了。例如在关机的时候我希望可以先执行两次sync同步化写入磁盘后才shutdown计算机,则可以在指令之间加;

sync; sync; shutdown -h now

cmd1 && cmd2
cmd1 执行完毕且正确执行($?=0),则开始执行 cmd2;若 cmd1 执行完毕且为错误 ($?≠0),则 cmd2 不执行。

#使用 ls 查阅目录 /tmp/abc 是否存在,若存在则用 touch 建立 /tmp/abc/hehe
ls /tmp/abc 2> /dev/null && touch /tmp/abc/hehe

cmd1 || cmd2
cmd1 执行完毕且正确执行($?=0),则 cmd2 不执行;若 cmd1 执行完毕且为错误 ($?≠0),则开始执行 cmd2

# 测试 /tmp/abc 是否存在,若不存在则予以建立,若存在就不作任何事情
rm -r /tmp/abc;
ls /tmp/abc || mkdir /tmp/abc

一般来说,假设判断式有三个,常见写法就是:command1 && command2 || command3

# 注意两条指令之间的区别
ls /tmp/vbirding &> /dev/null && echo "exist" || echo "not exist"
not exist
ls /tmp/vbirding &> /dev/null || echo "not exist" && echo "exist"
not exist
exist

4.3 管线命令(|)

管线命令|仅能处理经由前面一个指令传来的正确信息,也就是|前面指令的 stdout 信息,作为|后面指令的stdin,对于 stderr 并没有直接处理的能力。

# /etc内容较多,不便查阅,可利用less的功能
ls -al /etc | less

在每个管线后面接的第一个数据必定是指令!而且这个指令必须要能够接受 stdin 的数据才行,这样的指令才可以是为管线命令,例如 less, more, head, tail, cut, grep, sort, wc, uniq 等都是可以接受 stdin 的管线命令。至于例如 ls, cp, mv 等就不是管线命令了!因为它们并不会接受来自 stdin 的数据。

uniq [-ic]
    -i :忽略大小写字符的不同;
    -c :进行计数
last | cut -d ' ' -f1 | sort | uniq -ci

wc [-lwm]
    -l :仅列出行;
    -w :仅列出多少字(英文单字)-m :多少字符;
    没有参数:列出行数、单词数、字符数
cat /etc/passwd | wc

如果你硬要让 stderr 可以被管线命令所使用,其实也可以处理,利用数据流重导向2>&1,让 2> 变成 1>

# 双向重导向:tee,会同时将数据流分送到文件与屏幕(stdout)。
tee [-a] file
    -a :以累加 (append) 的方式,将数据加入 file 当中!
# 让gcc编译报错的stderr,通过管道命令tee输出至文件log
gcc main.c -o main 2>&1 | tee log

4.4 字符转换命令(tr、join、paste)

命令tr可以用来转换或删除文件中的字符!

tr [OPTION]... SET1 [SET2]
    -d :删除讯息当中的 SET1 这个字符串
    -s :缩减连续重复的字符成指定的单个字符
    没有参数:用SET2替换SET1,不会缩减连续重复的字符
# 将 last 输出的讯息中,所有的小写变成大写字符
last | tr '[a-z]' '[A-Z]'
# 将 /etc/passwd 输出的讯息中,将冒号 (:) 删除
cat /etc/passwd | tr -d ':'

命令join将两个文件中,指定栏位内容相同的行连接起来。找出两个文件中,指定栏位内容相同的行,并加以合并,再输出到标准输出设备。

【注】需要特别注意的是,在使用 join 之前,你所需要处理的文件应该要事先经过排序 (sort) 处理!否则有些比对的项目会被略过。

join [-ti12] file1 file2
    -a<12>:除了显示原来的输出内容之外,还显示指令文件中没有相同栏位的行
    -t<字符>:使用栏位的分隔字符,默认是空格
    -i :忽略大小写的差异;
    -1<栏位>:file1中要被用来分析的栏位,如果没有指定文件的栏位,默认是该文件的第一栏
    -2<栏位>:file2中要被用来分析的栏位
# 将文件a.csv join b.csv
cat a.csv
1,China
2,America
3,Russia
cat b.csv 
1,China,Beijing
2,America,Washington
3,Russia
# 将a.csv的第一栏与b.csv的第二栏做对比,如果相等,则将其整合成一列,且将用来分析的相同栏移到最前面
join -1 1 -2 2 -t',' -a 1 a.csv b.csv
join -2 2 -t',' -a 1 a.csv b.csv	# 默认a.csv的第一栏
1,China,Beijing
2,America,Washington
3,Russia

命令paste 就要比 join 简单多了!相对于join必须要比对两个文件的数据相关性, paste 就直接将两行贴在一起,且中间以 tab 键隔开而已!

paste [-d] file1 file2
	-d :后面可以接分隔字符,默认是tab键隔开
	 - :如果 file 部分写成 - ,表示来自 standard input
# 下面两条指令效果一样
paste -d "-" a.csv b.csv
cat a.csv | paste -d "-" - b.csv
1,China-Beijing,1
2,America-Washington,2
3,Russia-Mosco,4

4.5 xargs 与 - 的用途

xargs 可以读入 stdin 的数据,并且以空格符断行字符作为分辨,将 stdin 的资料分隔成为 arguments 。 因为是以空格符作为分隔,所以,如果有一些档名或者是其他意义的名词内含有空格符的时候, xargs 可能就会误判了。

xargs [-0pnd] command
    -0:如果输入的 stdin 含有特殊字符,例如 `, \, 空格键等等字符时,这个 -0 参数可以将他还原成一般字符
    -p:当每次执行一个argument的时候询问一次用户
    -n num:表示命令在执行的时候一次用的argument的个数,默认是用所有的
    -d delim:默认的xargs分隔符是回车,argument的分隔符是空格,这里修改的是xargs的分隔符
    无参数:当 xargs 后面没有接任何的指令时,默认是以 echo 来进行输出喔!

# 多行输入用echo单行输出
cat a.csv | xargs 
1,China 2,America 3,Russia

# 每次执行用两个参数
cat a.csv | xargs -n2
1,China 2,America
3,Russia

# 用 rm 删除太多的文件时候,可能得到一个错误信息:/bin/rm Argument list too long. 用 xargs 去避免这个问题
find . -type f -name "*.log" | xargs rm -f

# 统计一个源代码目录中所有 php 文件的行数
find . -type f -name "*.php" | xargs wc -l

# 查找所有的 jpg 文件,并且压缩它们
find . -type f -name "*.jpg" | xargs tar -czvf images.tar.gz

在管线命令当中,常常会使用到前一个指令的 stdout 作为这次的stdin的一部分内容。

# paste中用过的一个例子
cat a.csv | paste -d "-" - b.csv

# 将 /home 里面的文件给他打包,但打包的数据不是纪录到文件,而是传送到 stdout;
# 经过管线后,将 tar -cvf - /home 传送给后面的 tar -xvf - 。
# 后面的这个 - 则是取用前一个指令的 stdout, 因此,我们就不需要使用 filename 了!
mkdir /tmp/homeback
tar -cvf - /home | tar -xvf - -C /tmp/homeback

五、shell中常用命令

5.1 程序出错,中断整个脚本

linux所有命令执行返回的状态值:0表示没有错误,其他任何值表明有错误。

如果只检擦某一条指令的返回状态是否正确,可以使用下面的方式。

if
	[ $? -ne 0 ]
	then
	return $?
fi

如果要检查shell脚本中的每一条指令的返回 状态,则可以用下面的方式。

set -e	# 程序中任意一条命令返回非0的值,终端shell程序

5.2 操作指定时间内更新过的文件

find找到./delve目录下10分钟内修改过的.go文件,然后将这些文件通过scp赋值到23机器的指定目录下面。find命令scp命令之前总结过。

for file in $(find ./delve -mmin -10 -name "*.go")
do
  dir=$(dirname $file)
  dest=23:/home/huangqiqi/golang/delve_log${dir#"./delve"}
  scp -r $file $dest
done
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值