0x01 序※
Shell 是一种命令行解释器,它是一种用于与操作系统内核通信的用户界面。Shell 脚本是一种用于编写自动化任务和系统管理的脚本语言。以下是 Shell 脚本的基本语法:
0x02 注释:※
以 #
开头的内容表示注释,不会被解释器执行。
# 这是一行注释
#!(也称为shebang或hashbang)是一个特殊的字符序列,通常出现在脚本文件的第一行。它的作用是指定用于执行该脚本的解释器或执行程序。
当操作系统(例如Unix、Linux或类似系统)执行一个文件时,会检查文件的第一行,如果以#!开头,操作系统会将紧随其后的路径作为解释器或执行程序的路径,并将该文件作为参数传递给指定的解释器或执行程序。
例如,在一个Python脚本文件中,你可能会看到以下行:
#!/usr/bin/env python
# 这行告诉操作系统,当执行这个脚本时,请使用位于/usr/bin/env路径下的Python解释器来执行它。
在shell脚本中,你可能会看到:
#!/bin/bash
# 这行告诉操作系统,使用bash解释器来执行这个脚本。
总之,#!是一种用于指定脚本文件的解释器或执行程序的约定,它使得脚本文件可以独立于特定的解释器或执行程序而运行。
0x03 数据类型※
基本数据类型※
字符串(String):
- 在 shell 中,字符串是最常见的数据类型。可以使用单引号或双引号来表示字符串,也可以进行字符串拼接、截取和替换等操作。
示例:
str1="Hello" str2='World' combined="$str1 $str2"
整数(Integer):
- 在一些支持的 shell 中,可以使用整数变量。通常在进行算术运算时会用到整数类型。
示例:
num=10
数组(Array):
- 一些 shell 支持数组,可以用来存储一组相关的数据。数组可以通过下标进行访问和操作。
示例:
arr=(1 2 3 4 5) echo ${arr[2]} # 输出数组的第三个元素 另外,还可以将数组定义成一组“索引值”: array_var[0]="test1" array_var[1]="test2" array_var[2]="test3" ... 打印出特定索引的数组元素内容: echo ${array_var[0]} test1 index=5 echo ${array_var[$index]} test6 以列表形式打印出数组中的所有值: $ echo ${array_var[*]} test1 test2 test3 test4 test5 test6 也可以这样使用: $ echo ${array_var[@]} test1 test2 test3 test4 test5 test6 打印数组长度(即数组中元素的个数): $ echo ${#array_var[*]}6
关联数组(Associative Array):
- 一些高级的 shell 支持关联数组,它们使用字符串作为索引,而不是数字。这使得可以创建更灵活的数据结构。
示例:
declare -A assoc_arr assoc_arr["key1"]="value1" assoc_arr["key2"]="value2" 每一个数组元素都有对应的索引。普通数组和关联数组的索引类型不同。我们可以用下面的 方法获取数组的索引列表: $ echo ${!array_var[*]} 也可以这样 $ echo ${!array_var[@]}
- 特殊变量:
- 环境变量(Environment Variables): 这些变量由操作系统或用户在启动shell时设置,在整个shell会话期间都是可用的。一些常见的环境变量包括:
PATH
:用于指定可执行文件的搜索路径。HOME
:当前用户的主目录路径。USER
:当前用户名。SHELL
:当前使用的shell解释器。所有的应用程序和脚本都可以访问环境变量。可以使用env或printenv命令查看当前shell
中所定义的全部环境变量:$> env
PWD=/home/clif/ShellCookBook
HOME=/home/clif
SHELL=/bin/bash
# …… 其他行
要查看其他进程的环境变量,可以使用如下命令:cat /proc/$PID/environ
其中,PID是相关进程的进程ID(PID是一个整数)。
- 位置参数变量(Positional Parameters): 这些变量用于存储脚本或函数的参数。
\$0
:脚本的名称。\$1
,\$2
, ...:第一个、第二个参数,依此类推。shift命令可以将参数依次向左移动一个位置,让脚本能够使用$1来访问到每一个参数。$*
:所有参数作为一个单词。$@
:所有参数作为多个单词。$$
:当前进程的进程ID。
- 特殊变量(Special Variables): 这些变量具有特殊用途,用于控制脚本的行为。
$$
:当前shell进程的进程ID。$?
:上一个命令的退出状态。$!
:最后一个在后台运行的进程的进程ID。
- 用户定义变量(User-Defined Variables): 用户可以自定义变量来存储数据。
myVar="Hello"
:定义一个名为myVar
的变量并赋值为Hello
。
- shell内建变量
length=${#var}
:获得var的长度$SHELL
:获得当前所在shell
- 环境变量(Environment Variables): 这些变量由操作系统或用户在启动shell时设置,在整个shell会话期间都是可用的。一些常见的环境变量包括:
- 别名:
别名就是一种便捷方式,可以为用户省去输入一长串命令序列的麻烦。使用alias命令创建别名、使用unalias删除别名。
$ alias new_command='command sequence' # 如果身份为特权用户,别名也会造成安全问题。为了避免对系统造成危害,你应该将命令转义。 $ \command # 字符\可以转义命令,从而执行原本的命令。在不可信环境下执行特权命令时,在命令前加 上\来忽略可能存在的别名总是一种良好的安全实践。这是因为攻击者可能已经将一些别有用心 的命令利用别名伪装成了特权命令,借此来盗取用户输入的重要信息。 alias命令无参数可以列出当前定义的所有别名
变量※
变量名由一系列字母、数字和下划线组成,其中不包含空白字符。常用的惯例是在脚本中使用大写字母命名环境变量,使用驼峰命名法或小写字母命名其他变量
可以使用等号操作符为变量赋值:varName=value
varName是变量名,value是赋给变量的值。如果value不包含任何空白字符(例如空格),那么就不需要将其放入引号中,否则必须使用单引号或双引号。
注意,var = value不同于var=value。把var=value写成var = value是一个常见的错误。两边没有空格的等号是赋值操作符,加上空格的等号表示的是等量关系测试。
在 Shell 中,变量名不需要事先声明类型,直接赋值即可。
name="John"
引用变量:※
使用 $
符号引用变量的值。
echo "My name is $name"
0x04 流程控制:※
条件语句:※
if [ condition ]; then
# some commands
elif [ condition ]; then
# some other commands
else
# fallback commands
fi
循环语句:※
for item in list; do
# some commands using $item
done
或者
while [ condition ]; do
# some commands
done
0x05 函数:※
function_name() {
# function body
}
0x06 管道:※
将一个命令的输出作为另一个命令的输入。
command1 | command2
在Unix和类Unix系统中,管道是一种非常有用的机制,用于将一个进程的输出直接传递给另一个进程的输入,从而实现进程间通信和协作。管道可以分为无名管道(匿名管道)和有名管道(FIFO)。
匿名管道: 无名管道是最常见的管道形式,它是一种临时的、单向的通信机制,只能在相关进程之间使用。无名管道使用竖线符号
|
连接两个或多个命令,将第一个命令的输出直接传递给第二个命令的输入。无名管道只能用于相关进程之间的通信,通常在父子进程或者通过一个shell脚本中使用。示例:
command1 | command2
有名管道(FIFO): 有名管道是一种命名的特殊文件,允许无关的进程之间进行通信。它是一种持久的通信机制,可以在文件系统中存在,并且可以通过文件名进行引用。有名管道通过
mkfifo
命令创建,并可以由不同的进程在不同的时间打开和关闭,从而实现进程间的通信。示例:
mkfifo mypipe command1 > mypipe command2 < mypipe
管道文件: 管道文件是一种特殊类型的文件,它们存在于文件系统中,并允许进程通过读取和写入文件来进行通信。这些文件通常位于
/dev/fd/
或/proc/self/fd/
目录下,它们允许进程以文件I/O的方式进行进程间通信。
补充:
"Fork 炸弹" 是一个旨在演示系统资源耗尽的恶作剧式命令。它利用了操作系统中进程的复制(fork)机制,通过不断复制自身来消耗系统资源,最终导致系统崩溃或变得无法响应。
Fork 炸弹的基本语法如下(在类 Unix 系统中):
:(){ :|: & };:
解释语法:
:(){ ... };
:这是一个函数定义,函数名为:
。在 shell 中,:
是一个有效的函数名,这里定义了一个递归调用的函数。:|: &
:在函数内部,函数自身被调用两次,用管道|
连接,并在后台运行&
。这导致函数不断复制自身。;
:表示命令分隔符,用于分隔不同的命令。:
:最后一个:
是用来执行函数的,这样函数就会开始递归调用自身。当这个函数被执行时,它会不断地复制自身,每次复制都会产生两个新的进程,这些进程又会继续复制自身,如此循环,系统资源会被迅速耗尽,最终导致系统崩溃或变得无法响应。
在大多数现代操作系统中,都会限制一个进程可以创建的子进程的数量。这个限制是为了防止恶意代码或者无意中的错误导致系统资源耗尽,从而影响系统的正常运行。当一个进程尝试创建过多的子进程时,操作系统会限制这个行为,通常会导致该进程无法继续创建新的子进程。
在 Linux 中,这个限制通常由内核参数
ulimit
控制。需要注意的是,Fork 炸弹是一个危险的命令,绝对不要在真实的系统上执行。这只是一个用于演示的恶作剧命令,它的目的是向人们展示系统资源耗尽的严重后果。
0x07 输入输出重定向:※
# 输出重定向
command > output.txt
# 输入重定向
command < input.txt
在 Unix 和类 Unix 系统中,每个运行的进程都会默认打开三个文件描述符(fd),它们分别是标准输入(stdin,fd为0)、标准输出(stdout,fd为1)和标准错误输出(stderr,fd为2)。这些文件描述符允许进程与其环境进行输入和输出交互。
类型 | 文件描述符 | 默认情况 | 对应文件句柄位置 |
---|---|---|---|
标准输入(standard input) | 0 | 从键盘获得输入 | /proc/self/fd/0 |
标准输出(standard output) | 1 | 输出到屏幕(即控制台) | /proc/self/fd/1 |
错误输出(error output) | 2 | 输出到屏幕(即控制台) | /proc/self/fd/2 |
输入输出重定向允许将一个进程的标准输入和标准输出(以及标准错误)重定向到其他位置,比如文件或者其他进程。这使得进程可以从文件中读取输入,或者将输出写入到文件中,而不是直接与终端进行交互。
在 shell 中,可以使用一些特殊的符号来进行输入输出重定向:
<
:用于将一个文件的内容重定向为标准输入。>
:用于将标准输出重定向到一个文件中,如果文件不存在则会创建,如果存在则会覆盖。>>
:用于将标准输出重定向到一个文件中,如果文件不存在则会创建,如果存在则会追加内容。2>
:用于将标准错误输出重定向到一个文件中,如果文件不存在则会创建,如果存在则会覆盖。2>>
:用于将标准错误输出重定向到一个文件中,如果文件不存在则会创建,如果存在则会追加内容。
这些符号可以与命令一起使用,来指示 shell 在执行命令时如何处理输入和输出。
例如:
command >/dev/null 2>&1
这里的含义是:
>/dev/null:将标准输出重定向到 /dev/null,也就是将命令1的标准输出丢弃。
2>&1:将标准错误重定向到与标准输出相同的位置,也就是 /dev/null,这样标准错误也会被丢弃。
但是位置交换结果就完全不同
command 2>&1 >/dev/null
前者2>&1把标准错误重定向到标准输出上,也就是屏幕
后者>/dev/null默认把标准输出重定向到/dev/null也就是丢弃了
于是程序把标准输出丢弃,把标准错误输出给屏幕了
0x08 区别()、[]、{}、''、""、``、※
圆括号
()
:- 在shell中,圆括号通常用于创建子shell或在子shell中执行命令。这意味着圆括号内的命令会在一个子shell中执行,而不会影响到父shell的环境。
- 圆括号还用于定义数组。在某些shell中,圆括号可以用于创建数组,如
arr=(1 2 3 4)
。
示例:
(command1; command2) # 在子shell中执行多个命令
方括号
[]
:- 在shell中,方括号通常用于条件测试和条件表达式。它们可以进行各种条件判断,包括数值比较、字符串匹配、文件测试等操作。
- 方括号内的表达式可以进行比较、字符串匹配、文件测试等操作。
示例:
if [ "$var" = "value" ]; then echo "The variable is equal to value." fi
大括号
{}
:- 大括号通常用于扩展变量或进行字符串操作。在shell中,大括号还用于创建代码块,例如在循环或条件语句中。
- 大括号还可以用于生成序列,如
{1..5}
会展开成1 2 3 4 5
。
示例:
echo file{1..3}.txt # 扩展成 file1.txt file2.txt file3.txt
for i in {1..5}; do echo "Number $i" done
单引号
''
:- 在单引号中,所有字符都被视为普通字符,不进行任何转义或扩展。这意味着变量和命令替换都不会发生。
- 单引号通常用于保护特殊字符,使其不被shell解释器处理。
示例:
echo 'This is a $variable' # 输出为 This is a $variable
双引号
""
:- 在双引号中,变量和命令替换会被执行,但是特殊字符的含义会得到保留(例如换行符
\n
)。 - 双引号通常用于包裹包含变量或特殊字符的字符串。
示例:
echo "The value of the variable is $variable" # 输出为 The value of the variable is [value of $variable]
- 在双引号中,变量和命令替换会被执行,但是特殊字符的含义会得到保留(例如换行符
反引号 ``:
- 反引号用于执行命令替换,将反引号内的命令执行结果作为字符串返回。
- 在现代shell中,推荐使用
$()
语法来代替反引号,因为它更易读且支持嵌套。
示例:
result=`command` # 将command的输出赋值给result
或
result=$(command) # 同样将command的输出赋值给result
0x09 文件测试:※
用于检查文件类型和属性。
if [ -f "file.txt" ]; then
# file.txt 存在且是一个普通文件
fi
以下是一些常用的文件测试操作符:
-e file
:检查文件是否存在。-f file
:检查文件是否存在且为普通文件。-d file
:检查文件是否存在且为目录。-s file
:检查文件是否存在且大小不为零。-r file
:检查文件是否存在且可读。-w file
:检查文件是否存在且可写。-x file
:检查文件是否存在且可执行。-L file
:检查文件是否存在且为符号链接。-S file
:检查文件是否存在且为套接字文件。-p file
:检查文件是否存在且为命名管道(FIFO)。-b file
:检查文件是否存在且为块设备文件。-c file
:检查文件是否存在且为字符设备文件。
这些测试操作符可以在条件语句中使用,例如:
if [ -f "file.txt" ]; then
echo "file.txt 存在且是一个普通文件"
fi
在这个例子中,-f
测试操作符用于检查文件 "file.txt" 是否存在且为一个普通文件。如果条件成立,就会执行 echo
命令。
0x0a 跋※
参考书目:linux shell脚本攻略
好玩的博客: The Unix School