Shell小结

shell

当我们启动Terminal时候,在第一行最后会出现ttys0000

1
2
3
Last login: Thu Aug 22 08:30:58 on ttys000
This is .zshrc shell script ...
%

tty代表电传打字机(teletypewriter),用于发送消息的机器

常用命令

man + cmd : 查看指令的说明

用来访问存储在Linux系统上的手册页面。在想要查找的工具的名称前面输入man命 令,就可以找到那个工具相应的手册条目

1
2
3
4
5
6
man ls
---
LS(1) General Commands Manual LS(1)

NAME
ls – list directory contents

使用q 退出

cd : 切换目录

这里是一些高级的用法,常规的用法不在此处介绍

  • 直接 使用 cd 或者 cd ~ 会回到 HOME 目录下
  • 使用 cd - 会返回 back to 上一个目录

单点符(.),表示当前目录;

双点符(..),表示当前目录的父目录

1
2
3
lingxiao@lingxiaodeMacBook-Air Desktop % cd .
lingxiao@lingxiaodeMacBook-Air Desktop % cd ..
%

pwd : 查看当前所在的绝对路径

  • 绝对路径 一定以/开头
  • 相对路径 一定不以/开都

ls 文件和目录列表

基础用法:(略)

可以使用 man ls 查看更过用法

ls -F

-F参数在目录名后加了正斜线(/); 在可执行文件后面加了*

1
2
3
4
% ls -F
OSToDiags* OSToDiags.spec dist/
OSToDiags.py build/ serialTool.py
%

ls -F -R 或者 ls -FR

递归查看

ls -l 显示长列表

1
2
3
4
5
6
7
total 8064
-rwxr-xr-x@ 1 lingxiao staff 4115408 Aug 19 15:40 OSToDiags
-rw-r--r--@ 1 lingxiao staff 3384 Aug 19 14:44 OSToDiags.py
-rw-r--r-- 1 lingxiao staff 718 Aug 19 15:40 OSToDiags.spec
drwxr-xr-x 3 lingxiao staff 96 Aug 19 15:40 build
drwxr-xr-x 3 lingxiao staff 96 Aug 22 08:54 dist
-rw-r--r--@ 1 lingxiao staff 2796 Aug 20 08:58 serialTool.py

touch file - 创建文件

cp source destination - 复制文件

mv a b - 重命名文件或者移动文件

rm -i file - 删除文件 注意一定要加上 “i”选项

mkdir dir - 创建目录

mkdir -p parentPath/SubPath

rmdir dir - 删除目录

默认情况下,rmdir命令只删除空目录。因为我们在New_Dir目录下创建了一个文件my_file, 所以rmdir命令拒绝删除目录。

file + finlName - 查看文件类型

1
2
3
4
5
6
% file dist 
dist: directory
% file serialTool.py
serialTool.py: Python script text executable, Unicode text, UTF-8 text
% file OSToDiags.spec
OSToDiags.spec: ASCII text

cat + filename 查看文件

ps 查看进程

默认情况下,ps命令只会显示运行在当前控制台下的属于当前用户的进程。

1
2
3
4
% ps
PID TTY TIME CMD
6218 ttys000 0:01.49 -zsh
%

可以通过 man ps 查看更多用法

kill + PID结束对应PID的进程

killall+ProcessName 结束ProcessName的进程(支持通配符模式)

alias 查看所有设置的Shell别名

1
2
3
4
5
6
7
% alias    
py=python3
python=python3
run-help=man
which-command=whence
ykb=jekyll
%

使用多个命令

Shell中可以将多个命令放在一起;

1
2
3
4
5
6
7
8
9
10
11
12
13
 % date;who;cal
Mon Aug 26 08:31:51 CST 2024
lingxiao console Aug 24 09:19
lingxiao ttys000 Aug 26 08:31
August 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 31

%

脚本编程基础

1
#!/bin/bash

在通常的shell脚本中,井号(#)用作注释行。shell并不会处理shell脚本中的注释行。然而, shell脚本文件的第一行是个例外,#后面的惊叹号会告诉shell用哪个shell来运行脚本.

Shell 在使用变量时候的注意点

$var 不明确的时候,可以使用**${var}**来使用变量

1
2
3
4
5
6
7
% var=Hello_
% echo "$varWorld" #此处会把`varWorld`当成变量,没法区分`var`变量

% echo "${var}Wrold" #此处使用 ${var}来区分出变量
Hello_Wrold
~ %

命令替换

意思是从命令输出中提取信息,并将其赋给变量。

命令替换的两种方式

  • 反引号字符(`)
  • $()格式
1
2
3
4
5
6
7
#!/bin/sh

testStr=`date`
echo "CurrentDate is $testStr"

testStr2=$(date)
echo "date--- is $testStr2"

输出如下:

CurrentDate is 2024年 8月23日 星期五 13时45分14秒 CST
date— is 2024年 8月23日 星期五 13时45分14秒 CST

运算

shell 的运算通常有两种方式

  • expr

    使用教程:略

  • 使用方括号

    1
    2
    var=$[1 + 5]
    echo $var ## 6

    注意在赋值的时候,需要再方括号前添加$符号

退出脚本

shell中运行的每个命令都使用退出状态码(exit status)告诉shell它已经运行完毕。退出状态 码是一个0~255的整数值,在命令结束运行时由命令传给shell。可以捕获这个值并在脚本中使用。

通过 $? 来获取状态码

  • 0 表示成功
  • 其他正直表示错误

当然,我们也可以自己指定退出的状态码 通过 exit code,请注意 这个code 的最大值不超过255.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#!/bin/sh

function test1() {
echo $(date)
}

test1
echo $? ## 0

function test2() {
echo $(who)
exit 5 ## 这里就已经退出了 当前Shell 进程,并且这个状态码5会在上一次Shell的进程中获取到
}
test2

# 注意一下的代码不会被执行
echo $?

# 这里的也不会被执行
echo $(cal)
exit 8
echo "--------"
echo $?

通过终端调用上述脚本,我们能够看到

1
2
3
4
5
6
7
% ./004-退出状态码.sh
2024年 8月23日 星期五 15时17分54秒 CST
0
lingxiao console Aug 21 09:15 lingxiao ttys000 Aug 23 15:07
% echo $?
5
%

Shell 中的 决策树,(making decision)

最简单的if

1
2
3
4
5
6
7
8
if pwd; then
echo "It works ('if' and 'then' locate at some line, must using "\;" to seperation)"
fi

if pwd
then
echo "It works"
fi

当 if 和 then 在一行的时候,需要再 if 条件语句 添加;; 如果不在一行就不用

fi : 是 finish 的缩写,表示 if 语句结束了

值得注意的是,当if 后面的语句是 命令的时候那么,其执行完成之后的退出状态为0(即$? == 0) 时,表示成功,会继续执行 then部分的语句。

多个条件判断的时候,也可以使用逻辑组合

  • && 逻辑与
  • || 逻辑或

if - elif - else

ifelif 后面 必须跟 then, 如果在同一行,则需要";"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
test03() { 
if (( $1 == 1 )); then
echo "fist"
elif (( $1 == 2 )); then
echo "second"
else
echo "no body using"
fi
}
test03 1
test03 2
test03 3

## fist
## second
## no body using

测试判断条件 – test 命令

if 条件中的语句退出并返回状态码 为 0 的时候,才会执行 其then之后的逻辑;

test命令提供了在if-then语句中测试不同条件的途径。如果test命令中列出的条件成立,test命令就会退出并返回退出状态码0。如果条件不成立,test命令就会退出并返回非零的退出状态码,这使得 if-then语句不会再被执行。

!!! 如果不写test命令的condition部分,它会以非零的退出状态码退出,并执行else语句块

1
2
3
4
5
6
7
8
9
test04() {
var="FUll"
if test $var; then
echo "返回状态码True"
else
echo "返回状态码False"
fi
}
test04 # 返回状态码True

test命令可以判断三类条件:

  • 数值比较
  • 字符串比较
  • 文件比较

测试判断条件 – [ ]

!!! 第一个方括号之后和第二个方括号之前必须加上一个空格,否则就会报错

1
2
3
4
5
6
7
8
9
test05() {
var="Imnt"
if [ $var ]; then
echo "返回状态码True"
else
echo "返回状态码False"
fi
}
test05 # 返回状态码True

数值比较

  • eq : equal
  • ge: greate or equal
  • gt: greate than
  • le: less of equal
  • lt: less than
  • ne: not equal
1
2
3
4
5
6
7
8
9
10
test06() {
if [ $1 -gt $2 ]
then
echo "$1 > $2"
elif [ $1 -ge $2 ]
then
echo "$1 >= $2"
fi
}
test06 2 2

字符串比较

![image-20240826093025820](/Users/lingxiao/Library/Application Support/typora-user-images/image-20240826093025820.png)

!!! 大于号和小于号必须转义,否则shell会把它们当作重定向符号,把字符串值当作文件 名;

文件比较

检查目录

-d测试会检查指定的目录是否存在于系统中。如果你打算将文件写入目录或是准备切换到某 个目录中,先进行测试接着进行操作。

1
2
3
4
5
6
7
8
9
test07() {
if [ -d $1 ]; then
echo "this is directory"
cd $1
ls -F
else
echo "not a directory"
fi
}

If–then的高级用法

  • 用于数学表达式的双括号(( ... ))
  • 用于高级字符串处理功能的双方括号[[ ... ]]

双括号

双括号命令允许你在比较过程中使用高级数学表达式;

1
2
3
4
5
6
7
8
9
10
11

test08() {
var1=10
if (( $var1 ** 2 > 90 ))
then
(( var2 = $var1 ** 2 ))
# var3=(($var1 ** 2))
echo "The Square of $var1 is $var2"
fi
}
test08 # The Square of 10 is 100

注意:

  • 双括号里的高级的表达式 的运算和赋值只能在其内部进行!!!否则会报错
  • 不需要将双括号中表达式里的大于号转义。这是双括号命令提供的另一个高级特性

双中括号(双方括号)

双方括号命令提供了针对字符串比较的高级特性。双方括号命令的格式: [[ expression ]]

双方括号里的expression使用了test命令中采用的标准字符串比较。但它提供了test命令未提供的另一个特性——模式匹配(pattern matching)。

1
2
3
4
5
6
7
8
9
10
test09() {
if [[ $USER == l* ]] ## 这里用到了模式匹配
then
echo "HELLO $USER"
else
echo "sorry, who are u"
fi
}

test09 ## HELLO lingxiao

case 语句

类似于其他语言的switch语句

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
test10() {
case $1 in
rich | brarbara) # 多个条件可以使用 `|` 分割,相当于 逻辑 或
echo "Welcome. $1"
echo "Pls enjoy yourself"
;; ##相当于 break
testing)
echo "Sepcial Testing"
;;
jessica)
echo "I'm $1"
;;
*) ## 星号(`*`)会捕获所有与已 知模式不匹配的值
echo "Sorry,you are not allowed"
;;
esac
}

循环

for - 循环

1
2
3
for var in list do
commands
done

可以用for命令来自动遍历目录中的文件。进行此操作时,必须在文件名或路径名中 使用通配符(*)。它会强制shell使用文件扩展匹配。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
test02() {
## 如果想要遍历某一个文件夹下的所有内容,需要使用通配符
path="/Users/lingxiao/.personal/study_and_code/shell-tutorial/*"

# path="/Users/lingxiao/.personal/study_and_code/shell-tutorial"

for file in $path
do
# 这里需要注意下,如果file中包含 空格,需要把`$file` 通过 双引号 `"$file"` 包起来,否则会报错;(bash shell会将额外的单词当作参数,进而造成错误)
if [ -d "$file" ]; then
echo "$file is a directory"
elif [ -f "$file" ]; then
echo "$file is a raw file"
fi
done
}

for 循环更多练习,列出系统中所有在 $PATH 环境变量中指定目录下的可执行文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
test04() {
IFS=:
for folder in $PATH
do
echo "$folder:"
for file in $folder/*
do
if [ -x $file ]
then
echo " $file"
fi
done
done
}

方法中第一行 IFS=:解释

作用: 将Shell的内部字段分隔符(IFS)设置为冒号(:)。

原因: 在Shell中,IFS 用于指定在变量展开和命令替换时,如何分隔字符串。默认情况下,IFS 包含空格、制表符和换行符。将其设置为冒号后,可以方便地将 $PATH 环境变量按照冒号分隔,遍历其中的每个目录。

C-Style For循环

需要使用高级运算符 双括号, 同时 双括号 内部的变量 也不需要使用 $

1
2
3
4
5
6
test03() {
for (( var = 1; var < 10; var++)) ## 注意这里操作 var 不需要添加 `$`符号
do
echo "index == $var"
done
}

while循环

1
2
3
4
5
6
7
8
test01() {
declare var=0
while [ $var -lt 10 ]; do
# ((var++)) ##高级运算符里不需要带`$`
var=$[ $var + 1 ]
echo "$var"
done
}

循环输出的重定向

如下示例,在循环结束表示done后,添加重定向的命令

1
2
3
4
5
6
7
8
9
10
11
12
test02() {
## 如果想要遍历某一个文件夹下的所有内容,需要使用通配符
path="/Users/lingxiao/.personal/study_and_code/shell-tutorial/*"
for file in $path
do
if [ -d $file ]; then
echo "$file is a directory"
elif [ -f $file ]; then
echo "$file is a raw file"
fi
done >> ./Redirections/log.y0823 ## 重定向
}

处理用户输入

  • $0: 是程序名

  • $1、$2、...$8、$9、${10}、${11}:表示输入的参数

  • $# 表示参数的个数

  • ${!#}获取最后一个参数;

  • $*$@获取所有的参数

    注意 不是 ${$#}, 因为花括号里不能接着放美元符

如果只想要获取脚本名称可以使用basename命令

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
#!/bin/sh
echo "\$0 == $0" #此处获取的是脚本的路径

scriptName=$(basename $0) #此处获取 脚本的文件名称
echo $scriptName


echo "1 == $1"
echo "2 == $2"
echo "3 == $3"
echo "4 == $4"
echo "5 == $5"
echo "6 == $6"
echo "7 == $7"
echo "8 == $8"
echo "9 == $9"
echo "10 == ${10}"
echo "11== ${11}"
echo "12 == ${12}"

echo "\$# total args count is == $#"

echo "last args var is == ${$#}" ## 出错了last args var is == 69701,方括号里不能出现美元符
echo "last args var is == ${!#}" ## 把美元符改成!就OK last args var is == 12

echo "获取所有参数 \$*-- $*" # 获取所有参数 $*-- 1 2 3 4 5 6 7 8 9 10 11 12
echo "获取所有参数 \$@-- $@" # 获取所有参数 $@-- 1 2 3 4 5 6 7 8 9 10 11 12

$*$@的区别

$*$@在没有添加引号的时候,两者一样

当添加了引号,"$*" 会把所有的参数当成一个字符串,而"$@"会把参数当成一个数组

接着上述代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
## $* 和 $@ 的区别
#
echo "---------\"\$*\"-------"
count=1
#
for param in "$*"
do
echo "\$* Parameter #$count = $param"
count=$[ $count + 1 ]
done


echo "---------\"\$@\"-------"
count=1
#
for param in "$@"
do
echo "\$@ Parameter #$count = $param"
count=$[ $count + 1 ]
done

上述代码输出如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
---------"$*"-------
$* Parameter #1 = 1 2 3 4 5 6 7 8 9 10 11 12
---------"$@"-------
$@ Parameter #1 = 1
$@ Parameter #2 = 2
$@ Parameter #3 = 3
$@ Parameter #4 = 4
$@ Parameter #5 = 5
$@ Parameter #6 = 6
$@ Parameter #7 = 7
$@ Parameter #8 = 8
$@ Parameter #9 = 9
$@ Parameter #10 = 10
$@ Parameter #11 = 11
$@ Parameter #12 = 12

脚本中read命令

read命令从标准输入(键盘)或另一个文件描述符中接受输入。在收到输入后,read命令 会将数据放进一个变量。

1
2
3
4
5
6
7
#!/bin/sh
test01() {
echo "Enter Your Name : "
read name ##把输入的字符赋值给变量`name`
echo "Hello $name, Welcome To Our World"
}
test01

上述代码输出结果:
Enter Your Name :
lingxiao
Hello lingxiao, Welcome To Our World

Shell函数

基础概念

函数是一个脚本代码块,即一个代码块,这在其他语言中也是这样解释。

函数定义的两种方式

1
2
3
4
5
6
7
8
9
function func_1 { # 名称后没有小括号
echo "hello world 001"
}
func_1 ## hello world 001

func_2() { ## 名称后面会有 小括号
echo "hello world 002"
}
func_2 ## hello world 002

跟其他大多数一样,函数必须先声明才能使用。

案例:

输出 1到10 的数字

1
2
3
4
5
6
7
8
9
loop_test() {
count=0
while [ $count -lt 10 ]; do
count=$[ $count + 1 ]
echo "count == $count"
done
}

loop_test

返回值

Shell 会把函数当成一个小脚本,运行结束之后,会返回一个状态码

1. 默认退出状态码

默认情况下,函数退出的状态码就是函数内部最后一条指令的退出状态码,可以通过$?来获取。

我们接上述loop_test的函数来测试,我们能够看到返回的是0;标识函数运行成功

1
2
3
4

loop_test

echo $? ## 0

2. 使用return命令

shell使用return命令来退出函数并返回特定的退出状态码

1
2
3
4
5
6
7
8
double_value() {
read -p "Enter a value: " value ## -p 选项允许你在读取用户输入之前显示一个提示信息(prompt)。在这里,提示信息是 "Enter a value: "。
echo "Doubling The Value"
return $[ $value * 2 ] ## 返回特定的 退出状态码
}

double_value
echo "The New Value is $?"

上述代码的输出如下:

1
2
3
Enter a value: 100
Doubling The Value
The New Value is 200

这里需要注意两点

  1. 调用函数结束之后就需要执行获取状态码的命令$?

    如果在用$?变量提取函数返回值之前执行了其他命令,函数的返回值就会丢失。记住,$? 变量会返回执行的最后一条命令的退出状态码。

  2. 状态码的取值范围是0~255

所以如果要返回较大整数值或者是字符串的话,这种返回则不能够满足。

3.使用函数输出

我们函数的输出保存到一个变量中

1
2
3
4
5
6
7
function double_value2 {
read -p "Enter a value: " value
# echo "Doubling The Value"
echo $[ $value * 2 ]
}
result=$(double_value2) #注意这的函数调用,会把函数中所有 echo 的内容 赋值给 result
echo "The New Value2 is $result"

输出结果如下:

1
2
Enter a value: 125
The New Value2 is 250

新函数会用echo语句来显示计算的结果。该脚本会获取double_value2函数的输出,而不是查看退出状 态码。


Shell小结
https://jackiedai.github.io/2024/09/04/007unix_shell/003-Shell/
Author
lingXiao
Posted on
September 4, 2024
Licensed under