Shell

-
-
2024-05-10

 

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
  • 别名:
    • 别名就是一种便捷方式,可以为用户省去输入一长串命令序列的麻烦。使用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)。

  1. 匿名管道: 无名管道是最常见的管道形式,它是一种临时的、单向的通信机制,只能在相关进程之间使用。无名管道使用竖线符号 | 连接两个或多个命令,将第一个命令的输出直接传递给第二个命令的输入。无名管道只能用于相关进程之间的通信,通常在父子进程或者通过一个shell脚本中使用。

    示例:

    command1 | command2
    
  2. 有名管道(FIFO): 有名管道是一种命名的特殊文件,允许无关的进程之间进行通信。它是一种持久的通信机制,可以在文件系统中存在,并且可以通过文件名进行引用。有名管道通过mkfifo命令创建,并可以由不同的进程在不同的时间打开和关闭,从而实现进程间的通信。

    示例:

    mkfifo mypipe
    command1 > mypipe
    command2 < mypipe
    

管道文件: 管道文件是一种特殊类型的文件,它们存在于文件系统中,并允许进程通过读取和写入文件来进行通信。这些文件通常位于/dev/fd//proc/self/fd/目录下,它们允许进程以文件I/O的方式进行进程间通信。

补充:

"Fork 炸弹" 是一个旨在演示系统资源耗尽的恶作剧式命令。它利用了操作系统中进程的复制(fork)机制,通过不断复制自身来消耗系统资源,最终导致系统崩溃或变得无法响应。

Fork 炸弹的基本语法如下(在类 Unix 系统中):

:(){ :|: & };:

解释语法:

  1. :(){ ... };:这是一个函数定义,函数名为 :。在 shell 中,: 是一个有效的函数名,这里定义了一个递归调用的函数。
  2. :|: &:在函数内部,函数自身被调用两次,用管道 | 连接,并在后台运行 &。这导致函数不断复制自身。
  3. ;:表示命令分隔符,用于分隔不同的命令。
  4. ::最后一个 : 是用来执行函数的,这样函数就会开始递归调用自身。

当这个函数被执行时,它会不断地复制自身,每次复制都会产生两个新的进程,这些进程又会继续复制自身,如此循环,系统资源会被迅速耗尽,最终导致系统崩溃或变得无法响应。

在大多数现代操作系统中,都会限制一个进程可以创建的子进程的数量。这个限制是为了防止恶意代码或者无意中的错误导致系统资源耗尽,从而影响系统的正常运行。当一个进程尝试创建过多的子进程时,操作系统会限制这个行为,通常会导致该进程无法继续创建新的子进程。

在 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 区别()、[]、{}、''、""、``、

  1. 圆括号 ()

    • 在shell中,圆括号通常用于创建子shell或在子shell中执行命令。这意味着圆括号内的命令会在一个子shell中执行,而不会影响到父shell的环境。
    • 圆括号还用于定义数组。在某些shell中,圆括号可以用于创建数组,如arr=(1 2 3 4)

    示例:

    (command1; command2)  # 在子shell中执行多个命令
    
  2. 方括号 []

    • 在shell中,方括号通常用于条件测试和条件表达式。它们可以进行各种条件判断,包括数值比较、字符串匹配、文件测试等操作。
    • 方括号内的表达式可以进行比较、字符串匹配、文件测试等操作。

    示例:

    if [ "$var" = "value" ]; then
        echo "The variable is equal to value."
    fi
    
  3. 大括号 {}

    • 大括号通常用于扩展变量或进行字符串操作。在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
    
  4. 单引号 ''

    • 在单引号中,所有字符都被视为普通字符,不进行任何转义或扩展。这意味着变量和命令替换都不会发生。
    • 单引号通常用于保护特殊字符,使其不被shell解释器处理。

    示例:

    echo 'This is a $variable'  # 输出为 This is a $variable
    
  5. 双引号 ""

    • 在双引号中,变量和命令替换会被执行,但是特殊字符的含义会得到保留(例如换行符 \n)。
    • 双引号通常用于包裹包含变量或特殊字符的字符串。

    示例:

    echo "The value of the variable is $variable"  # 输出为 The value of the variable is [value of $variable]
    
  6. 反引号 ``:

    • 反引号用于执行命令替换,将反引号内的命令执行结果作为字符串返回。
    • 在现代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

“您的支持是我持续分享的动力”

微信收款码
微信
支付宝收款码
支付宝

目录