- shell 壳
- 在计算机科学中,shell 通常指的是操作系统的一个界面,它允许用户与操作系统交互
- Shell 是一个命令行解释器
- Shell 是一个用 C 语言编写的程序,它是用户使用 Linux 的桥梁
- Shell 既是一种命令语言,又是一种程序设计语言
- Shell 是指一种应用程序,这个应用程序提供了一个界面,用户通过这个界面访问操作系统内核的服务
- Shell 为用户提供了一个与计算机交互的界面,用户可以输入命令,Shell 解释并执行这些命令
- Shell 可以是图形化的(如 Windows 中的图形用户界面)或命令行的(如 Unix/Linux 中的 Bash、Zsh 或 Windows 中的 Command Prompt 和 PowerShell)
- 学习 shell 主要关注命令行 Shell,特别是在 Unix 和 Linux 环境中的那些
- 对于那些想要深入了解 Linux 和 Unix 的人来说,熟练掌握 Shell 是非常有用的。它不仅可以提高你的效率,还能帮助你更好地理解操作系统的工作原理。
- Shell 可以运行系统命令,例如创建、删除、移动文件和目录等
- Shell 还支持编写脚本,这使得自动化任务变得简单
- Shell 脚本可以包括条件、循环和变量等基本编程构造
- 管道
|
可以将一个命令的输出作为另一个命令的输入 - 重定向
>
或>>
可以将输出重定向到文件 - 常见的 Shell:
- Bash (Bourne Again Shell)
- Zsh (Z Shell)
- Fish (Friendly Interactive Shell)
- Csh (C Shell) 和 Tcsh
- Ksh (Korn Shell)
- Bash 是最常用的 Linux Shell,是许多 Linux 发行版的默认 Shell,免费且易用
- Zsh 是功能丰富的 Shell,与 Bash 兼容,提供了许多方便的功能和插件系统
- Fish 是易于使用的现代 Shell,提供了智能的自动完成功能
- Csh 和 Tcsh 是受 C 语言语法启发的 Shell
- Ksh 由 David Korn 在 AT&T Bell Labs 开发
- Shell 有助于提升效率
- 对于许多任务,使用命令行比使用图形界面更快、更简单
- 可以通过 Shell 脚本编写来自动化重复的任务
- 当远程连接到服务器时,通常只有命令行界面可用
- Shell 脚本(shell script),是一种为 shell 编写的脚本程序
- 业界所说的 shell 通常都是指 shell 脚本,但读者朋友要知道,shell 和 shell script 是两个不同的概念
- Shell 编程跟 JavaScript、php 编程一样,只要有一个能编写代码的文本编辑器和一个能解释执行的脚本解释器就可以了
- demo:第一个 shell 脚本,打印 hello world!
#!/bin/bash
echo "hello world!"
- 在一般情况下,人们并不区分 Bourne Shell 和 Bourne Again Shell,所以,像
#!/bin/sh
,它同样也可以改为#!/bin/bash
#!
告诉系统其后路径所指定的程序即是解释此脚本文件的 Shell 程序**#!**
是一个约定的标记,它告诉系统这个脚本需要什么解释器来执行,即使用哪一种 Shell**echo**
命令用于向窗口输出文本echo /ˈekoʊ/
- n. 回声
- v. 回响
- 运行 shell 脚本的两种方法
- 作为可执行程序
- 作为解释器参数
#!/bin/bash
echo "hello world!"
# 将 shell 脚本视作可执行程序来运行
chmod +x ./1.sh # 使脚本具有执行权限
./1.sh # 执行脚本
echo "hello world!"
# 直接运行解释器,其参数就是 shell 脚本的文件名
/bin/sh 1.sh
这种方式运行的脚本,不需要在第一行指定解释器信息,写了也没用
- shell 变量名的命名规则
- 以字母或下划线打头,后面可跟字母、数字、下划线
- 不能与 bash 里的关键字重名
- 查看 bash 所有关键字:
compgen -k
- 打开终端,输入
bash
,切换到bash
后,输入compgen -k
按下回车即可
- 打开终端,输入
- 写 shell 变量 xxx
- 在给 shell 变量赋值时,变量名和等号之间不能有空格
- 读 shell 变量 xxx
- 必须:在变量名前面加美元符
**$xxx**
- 可选:在变量名外边加上花括号,可以帮助解释器识别变量的边界
**${xxx}**
- 建议:给所有变量加上花括号
- 必须:在变量名前面加美元符
- demo:将 hello world 保存到变量中,然后打印
str="hello world!"
echo str # 会直接打印 str
echo $str # 会打印 str 变量的值
echo ${str} # 会打印 str 变量的值
- 变量两侧的花括号,可以帮助解释器识别变量的边界
str="2"
echo "1$str3" # 1
echo "1${str}3" # 123
- 已定义的变量,可以被重新定义
nickname="dahuyou"
echo ${nickname} # dahuyou
nickname="xiaohuyou"
echo ${nickname} # xiaohuyou
- 使用
readonly
命令可以将变量定义为只读变量,只读变量的值不能被改变
nickname="dahuyou"
echo ${nickname} # dahuyou
readonly nickname
nickname="xiaohuyou"
echo ${nickname} # dahuyou
- 删除变量
**unset xxx**
- 使用
**unset**
命令在 shell 中移除一个变量意味着该变量及其值完全被删除,就好像它从未被定义过一样
VARIABLE="Hello, World!"
echo $VARIABLE # 输出:Hello, World!
unset VARIABLE
echo $VARIABLE # 输出为空
**unset**
命令不能删除只读变量
VARIABLE="Hello, World!"
readonly VARIABLE
echo $VARIABLE # 输出:Hello, World!
unset VARIABLE
echo $VARIABLE # 输出:Hello, World!
- 局部变量(Local Variables)
- 局部变量在单一脚本或命令中定义,且只在该脚本或命令中可用
- 局部变量在脚本或命令中定义,仅在当前 shell 实例中有效,其他 shell 启动的程序不能访问局部变量
- 声明局部变量只需要在脚本或子 shell 中简单地赋值即可,比如
variable_name="hello world"
- 如果在函数内部,需要用到
local
关键字来声明局部变量,变量的作用范围仅限于该函数
#!/bin/bash
function printLocalVar() {
local localVar="I am a local variable!"
echo $localVar # 输出:I am a local variable!
}
printLocalVar
echo $localVar # 输出为空
- 环境变量(Environment Variables)
- 环境变量是可用于多个进程的变量
- 所有的程序,包括 shell 启动的程序,都能访问环境变量
- 全局环境变量
- 全局环境变量被设计成几乎在所有用户的会话和程序中可访问
- 全局环境变量通常是由系统初始化脚本(如在Linux中的
/etc/profile
或/etc/environment
)设置的 - 全局环境变量对所有用户和进程都是可见的,它们在整个系统中都是可访问的
- 一些常见的全局环境变量:
PATH
、HOME
、LANG
、TERM
PATH
:这个变量定义了shell 搜索可执行文件的目录序列。当您尝试运行一个程序时,shell 会查找这些目录来找到该程序。HOME
:这个变量指定了用户的家目录,通常用于 shell 和其他程序来确定用户的配置文件和其他个人数据的位置。LANG
:这个变量定义了用户的语言和地区设置,用于控制程序如何与用户交互。TERM
:这个变量描述了终端类型,用于确定如何控制和与终端交互。- 尽管这些变量可以在所有用户和进程中访问,但用户仍然可以在他们自己的会话中覆盖这些变量的值。
- 当一个进程启动时,它会从其父进程继承环境变量
- 子进程可以继承父进程的环境变量,但子进程对环境变量的修改不会影响父进程
- 所有的程序,包括 shell 启动的程序,都能访问环境变量
- 有些程序需要环境变量来保证其正常运行
- 必要的时候 shell 脚本也可以定义环境变量
$PATH
和$HOME
是常见的环境变量$PATH
定义了 shell 搜索命令的目录- 声明环境变量需要用到
export
命令,例如export variable_name="hello world"
- 环境变量的生命周期从其在特定 shell 会话中被创建的时刻开始,直到该会话结束或变量被明确删除为止
- 当你在一个 shell 会话中使用
export
命令创建或修改环境变量时,该环境变量在该会话中就变得可用了 - 如果从当前 shell 启动新的子进程(例如新的 shell 会话或其他命令),那么这些子进程会继承当前环境的环境变量。但是,这些子进程对环境变量的任何更改都不会影响父进程或兄弟进程。
- 当你结束当前的 shell 会话(例如关闭终端窗口或退出会话),与该会话相关的所有环境变量都会被销毁。
- 你可以使用
unset
命令明确地删除一个环境变量。执行此命令后,环境变量将不再存在于当前会话中,但不会影响其他已经继承了该环境变量的子进程。 - 默认情况下,你在一个 shell 会话中创建的环境变量不会持久保存。这意味着,当会话结束后,变量就不存在了。
- 如果你希望一个环境变量在多个会话或系统重启后仍然存在,你需要将其添加到启动脚本中,如
~/.bashrc
、~/.bash_profile
或~/.profile
(具体文件取决于你的系统和 shell)。
#!/bin/bash
echo "The PATH variable: $PATH"
export NEW_VAR="I am a new environment variable!"
echo $NEW_VAR
- Shell 变量(Shell Variables)
- 运行shell时,会同时存在三种变量
- 局部变量
- 环境变量
- Shell 变量
- Shell 变量是由 shell 本身使用和维护的
- Shell 变量可以是环境变量,也可以只是局部变量,具体取决于它们是否被导出
- 如果 Shell 变量使用
**export**
关键字进行导出,那么它们将成为环境变量,否则仅在当前 Shell 会话中有效 PS1
是控制命令提示符外观的 shell 变量IFS
是内部字段分隔符,它决定了 Bash 如何解释空格和制表符- 即使某些 shell 变量(如
PS1
)在很多情况下都存在,但不是所有的 shell 会话都将其视为环境变量。它们通常在特定的 shell 会话中定义并使用。 - 局部变量、环境变量、Shell 变量,这三种变量的区别主要在于它们的作用范围和用途。理解它们的不同之处可以帮助你更好地管理和使用这些变量,以及写出更健壮的 shell 脚本。
- 字符串是 shell 编程中最常用最有用的数据类型(除了数字和字符串,也没啥其它类型好用了)
- 字符串可以用单引号,也可以用双引号,也可以不用引号
- 单引号字符串的限制
- 单引号里的任何字符都会原样输出,单引号字符串中的变量是无效的
- 单引号字串中不能出现单独一个的单引号(对单引号使用转义符后也不行),但单引号可成对出现,作为字符串拼接使用
str="world"
echo 'hello, $str'
echo 'hello, '$str''
echo 'hello, ${str}'
# hello, $str
# hello, world
# hello, ${str}
- 双引号字符串
- 双引号里可以有变量
- 双引号里可以出现转义字符
str="world"
echo "hello $str"
echo "hello ${str}"
echo "hello \"$str\""
# hello world
# hello world
# hello "world"
echo
vs.echo -e
echo
本身只会输出明确提供给它的文本echo -e
允许解释后面的转义字符。这意味着,当你使用-e
选项时,可以使用如\n
(新行)、\t
(制表符)等转义序列,并且它们会被解释为它们代表的特殊字符
echo "Hello\nWorld"
echo -e "Hello\nWorld"
# Hello\nWorld
# Hello
# World
- 获取字符串变量 str 的长度:
${#str}
、${#str[0]}
str="hello world"
echo ${#str}
echo ${#str[0]}
# 11
# 11
- 字符串切片:
${str:<开始字符的索引>:<结束字符的索引>}
str="hello world"
echo ${str:1}
echo ${str:1:2}
echo ${str:1:4}
# ello world
# el
# ello
- Shell 数组中可以存放多个值
- bash 支持一维数组(不支持多维数组)
- 初始化时不需要定义数组大小
- 类似于 C 语言,shell 数组元素的下标由 0 开始编号
- 获取数组中的元素要利用下标,下标可以是整数或算术表达式,其值应大于或等于 0
- 在 Shell 中,用括号来表示数组,数组元素用”空格”符号分割开
**<数组名>=(<值1> <值2> ... <值n-1> <值n>)**
# 定义数组的常见写法
fruits=('Apple' 'Banana' 'Cherry')
fruits=(
'Apple'
'Banana'
'Cherry'
)
- 读数组指定元素
**${<数组名>[成员索引]}**
- 读数组所有元素
**${<数组名>[@]}**
、**${<数组名>[*]}**
- 读数组长度
**${#<数组名>[@]}**
、**${#<数组名>[*]}**
- 数组切片
**${<数组名>[@]:<开始索引>:<结束索引>}**
- 添加数组元素
**<数组名>+=(<值1> <值2> ... <值n-1> <值n>)**
- 删除指定数组元素
**unset <数组名>[成员索引]**
- 删除所有数组元素
**unset <数组名>**
#!/bin/bash
# 定义数组
fruits=('Apple' 'Banana' 'Cherry')
echo "${fruits[@]}"
# Apple Banana Cherry
# 使用索引赋值添加新元素
fruits[3]='Mango'
echo "${fruits[@]}"
# Apple Banana Cherry Mango
# 使用+=添加多个元素到数组
fruits+=('Orange' 'Peach')
echo "${fruits[@]}"
# Apple Banana Cherry Mango Orange Peach
# 访问数组的单个元素
echo "${fruits[0]}"
# Apple
# 访问数组的所有元素
echo "${fruits[@]}"
# Apple Banana Cherry Mango Orange Peach
# 获取数组的长度
echo "${#fruits[@]}"
# 6
# 数组切片:输出从索引1开始的2个元素
echo "${fruits[@]:1:2}"
# Banana Cherry
# 删除数组的一个元素
unset fruits[1]
echo "After removing Banana: ${frruits[@]}"
# After removing Banana:
# 通过 `read` 命令动态添加元素到数组
echo "Enter two new fruits (separated by space):"
read -a new_fruits
fruits+=("${new_fruits[@]}")
echo "${fruits[@]}"
# Enter two new fruits (separated by space):
# foo bar
# Apple Cherry Mango Orange Peach foo bar
# 删除整个数组
unset fruits
echo "Array after deletion: ${fruits[@]}" # 这里不会有输出,因为整个数组已被删除
# Array after deletion:
- shell 中的注释
- 单行注释,以
**#**
开头,后边跟注释内容 - 多行注释
:<<'END_COMMENT'...END_COMMENT
结构,这实际上是一个“Here Document”,但如果不做任何事情,它就可以像多行注释一样工作
- 单行注释,以
#!/bin/bash
echo "1"
# 这是一个单行注释
echo "2"
: <<'END_COMMENT'
这是一个多行注释
它跨越了多行
您可以在这里写任意多的行
END_COMMENT
echo "3"
:<<EOF
注释内容...
注释内容...
注释内容...
EOF
echo "4"
:<<'END'
注释内容...
注释内容...
注释内容...
END
echo "5"
:<<MY_END
注释内容...
注释内容...
注释内容...
MY_END
echo "6"
:<<xxx
注释内容...
注释内容...
注释内容...
xxx
echo "7"
:<<!
注释内容...
注释内容...
注释内容...
!
echo "8"
# 1
# 2
# 3
# 4
# 5
# 6
# 7
# 8
echo "1"
# PS:勘误
# doc:https://www.runoob.com/linux/linux-shell-variable.html
# 在菜鸟教程上看到一种多行注释的写法,实测时无法使用,简单记录一下
# 测试结果表示不能使用 ' 符号
# 下面这种多行注释的写法是有问题的
:<<'
注释内容...
注释内容...
注释内容...
'
echo "2"
# 1
- 我们可以在执行 Shell 脚本时,向脚本传递参数
- shell 脚本内获取参数的格式为:
$n
,其中n
代表一个数字,1
为执行脚本的第一个参数,2
为执行脚本的第二个参数,以此类推…… - 获取传递到脚本的参数个数
$#
- 获取所有参数
$*
、$@
- 获取运行脚本的进程的 id
$$
- 后台运行的最后一个进程的 id 号
$!
- 显示Shell使用的当前选项,与set命令功能相同
$-
- 显示最后命令的退出状态。0表示没有错误,其他任何值表明有错误
$?
# 获取指定参数
echo "执行的文件名:$0";
echo "第一个参数为:$1";
echo "第二个参数为:$2";
echo "第三个参数为:$3";
# 获取参数个数
echo "参数个数:$#"
# 打印所有参数
echo "所有参数:$*"
echo "所有参数:$@"
# 运行脚本的进程的 id
echo "$$"
# 后台运行的最后一个进程的 id 号
echo "$!"
# 显示 Shell 使用的当前选项
echo "$-"
# 显示最后命令的退出状态
echo "$?"
$*
vs.$@
- 相同点:都是引用所有参数
- 不同点:只有在双引号中体现出来。假设在脚本运行时写了三个参数 1、2、3,则 “ * “ 等价于 “1 2 3”(传递了一个参数),而 “@” 等价于 “1” “2” “3”(传递了三个参数)
echo "-- \$* 演示 ---"
for i in "$*"; do
echo $i
done
echo "-- \$@ 演示 ---"
for i in "$@"; do
echo $i
done
# -- $* 演示 ---
# 1 hello world
# -- $@ 演示 ---
# 1
# hello
# world
- Bash 支持关联数组,可以使用任意的字符串、或者整数作为下标来访问数组元素
- 关联数组
- 在 bash 中,关联数组(有时也被称为哈希或字典)
- 关联数组的键是唯一的
- 关联数组允许使用字符串,而不是只有数字作为数组的索引
- 关联数组可以使用
declare
或typeset
命令声明 - 声明关联数组的语法:
declare -A <数组名>
-A
选项就是用于声明一个关联数组- 关联数组是 bash 版本4及更高版本的功能。如果你正在使用的 bash 版本低于4,那么关联数组将不可用
- 查看 bash 版本:
echo $BASH_VERSION
- demo:打印 mac 电脑上安装的所有应用程序
在 mac 上,应用程序默认都装在 applications 目录下边,我们可以使用一个简单的循环遍历 applications 目录,将文件名挨个 echo 出来。
for file in `ls /applications`; do
echo $file
done