002.文本处理和用户管理

sed

sed命令语法:sed [参数信息/选项信息] '条件信息 指令信息' 文件信息

sed命令执行过程

  1. 一次从输入中读取一行数据,输入可以是从文件中读取或从终端读取
  2. 依据输入的字符匹配数据
  3. 按照输入的命令修改数据
  4. 将修改后的数据输出到STDOUT

sed从文件中读取文本时,每换一行就会重新走一遍工作流程;默认情况下,sed每从输入中读取一行数据,不论是否满足匹配条件都会将其输出到屏幕,-n选项则是只输出满足条件的行

示例:新建测试文件

101,oldboy,CEO
102,zhaoyao,CTO
103,Alex,COO
104,yy,CFO
105,feixue,CIO

示例:sed查询操作

# 1. 按行号查询
sed -n "3p" person.txt    # 查找单行信息
sed -n "3,5p" person.txt    # 查看连续的多行信息
sed -n '3p;5p' person.txt    # 查询不连续的多行信息
sed -n '1~2p' person.txt    # 从第1行开始间隔为2查询信息

# 2. 按字符信息查询
sed -n '/oldboy/p' person.txt    # 查询存在字符串oldboy的行
sed -n '/oldboy/,/Alex/p' person.txt    # 查询oldboy到Alex之间的多行信息
    # 如果后一个字符串Alex没有匹配到,则会显示oldboy后的所有行
    # 如果后一个字符串Alex在文件内容中存在多个,sed会一直匹配到最后一个

示例:sed增加操作

sed '1a oldboy123' person.txt    # a表示addend,第1行的下一行添加一行内容oldboy123
sed '1i oldboy123' person.txt    # i表示insert,第1行的上一行添加一行内容
    # 如果不指定条件1时,默认所有信息都匹配条件,所有行都会执行sed添加操作
sed '$a oldboy123' person.txt    # 尾部添加行
sed '3a oldboy123\noldboy456' person.txt    # 利用转义字符添加多行内容

示例:sed删除操作

sed '3d' person.txt    # d表示delete,删除第3行
sed -i.bak '3d' person.txt    # 修改文件内容的同时生成原文件的.bak备份
    # 实际上sed命令在不添加-i选项时,所有操作都是临时操作,不会对源文件做出任何改变
sed '/^$/d' person.txt    # 删除空行

示例:sed替换操作

sed '3c This third paragraph' person.txt    # 整行替换
sed -n '5s#yy#hebor#gp' person.txt    # 替换某一行的部分内容
    # 使用sed命令替换文件信息时,建议不要同时使用-n和-i,否则替换后的文件不会保存默认输出信息,可能意味着会缺失很多信息
sed "s#$var#testworld#g" person.txt    # 将变量var的值替换成字符串testworld
sed -rn "/oldboy/s#(^.*)CEO#\1UFO#gp" person.txt    # 后项引用前项
    echo 123456 | sed -r "s#.*#<&>#g"    # &符号表示前项匹配到的所有内容
    echo 123456 | sed -r "s#[1-6]{1}#<&>#g"

示例:将.jpg文件修改为.txt文件

# 1. 找出需要修改的文件
ls *jpg | xargs -n 1    # 默认ls命令横向显示结果,xargs命令既可以用于横向显示结果,也可用于纵向显示结果

# 2. 模拟修改文件名称
ls *jpg | xargs -n 1 | sed -r "s#(.*)jpg#mv \1jpg \1txt#g"

# 3. 修改文件名
ls *jpg | xargs -n 1 | sed -r "s#(.*)jpg#mv \1jpg \1txt#g" | bash

ls *txt | xargs -n 1 | sed -r "s#(.*)txt#mv & \1jpg#g" | bash    # 将.txt修改回.jpg
rename .jpg .txt test*.jpg    # 专业的名称替换命令

示例:sed扩展应用

sed -r '/oldboy/d' person.txt    # 排除行内容中带有 oldboy 字符串的行
sed -n '/oldboy/!p' person.txt    # sed指令搜索结果取反。取反符只能用在sed指令符号前面,也就是p符号前面

sed '/^$/d' test.txt | sed '=' | xargs -n 2    # sed取行内容显示行号。xagrs划分段落时会忽略空行,所以需要先排除空行,sed的"="符表示显示行号
sed "/^$/d; =" test.txt | xargs -n 2 -L 2    # 上例优化。通过;号分隔sed的多个命令

echo student{01..05} | xargs -n 1 | sed 's#.*#useradd & \&\& echo &:$(tr -cd "[[:alnum:]]" < /dev/urandom | head -c 6) | tee -a ./passwd.log | chpasswd#g' | bash    # 批量创建用户,并将随机设置的6位密码保存到指定文件

-L 2参数表示xargs命令每次仅传递2行文本内容作为参数,在这个示例中如果不使用-L参数,xargs默认以空格来分割文本内容,这将会导致分割后的内容难以查看

补充:sed操作指令

[!]p:显示输出信息(input)
[!]i:插入文字信息(insert)
[!]a:追加文字信息(append)
[!]d:删除文字信息(delete)
[!]s:替换文字信息(substitution)
[!]c:整行信息替换

awk

gawk - pattern scanning and processing language,awk全名gawk

命令语法:awk [参数信息 -F -v]' '模式信息{动作信息}' 文件信息

awk命令执行过程

  1. 按行读取文件信息
  2. 判断是否符合匹配条件
  3. 匹配条件时,按执行动作处理(awk是没有默认输出的,如果没有执行动作print,则不会打印输出)
  4. 不匹配条件时,继续读取下一行重复上述过程,直至文件结尾

示例:创建测试环境文件

cat > awk_test.txt << EOF
> Zhang Dandan 41117397 :250:100:175
> Zhang Xiaoyu 390320151 :155:90:201
> Meng Waiwai 70271111 :250:80:75
> Wu Feixue 80042789 :250:60:50
> Liu Bingbing 41117483 :250:100:175
> Wang Xiaoai 3515064655 :50:168:200
> Zi Gege 1986787350 :250:168:200
> Li Youjiu 918391635 :175:75:300
> Lao Nanhai 918391635 :250:100:175
> EOF

cp awk_test.txt{,.bak}    # 备份源文件
column -t awk_test.txt.bak > awk_test.txt    # 见文件内容以表格形式展示

简单示例

# 示例:显示Xiaoyu的姓和ID
awk '/Xiaoyu/{print $1, $3}' awk_test.txt
    # 使用-F指定分隔符信息时,awk默认使用空格作为分隔列,且多个空格信息默认看作一个整体
    # awk默认将逗号识别成空格符号,如果需要将其识别为逗号,需要用引号包裹逗号
awk 'NR==2{print $1,$3}' awk_test.txt    # 另一种方式实现效果

# 示例:显示所有Zhang姓,并显示第二次捐款金额及名称
awk -F "[ :]+" '/Zhang/{print $2,$5}' awk_test.txt
    # 使用awk输出列信息时总是从左往右数,awk默认也可以从右往左数,需要用到参数$NF(Number Field)
awk -F "[ :]+" '/Zhang/{print $2,$(NF-1)}' awk_test.txt    # 另一种实现方式
    # 默认情况下NF代表最后一行,如果要取倒数第2行,则用NF-1即可,很明显awk的字符运算优先级高于算数运算,所有NF-1需要括号

# 示例:显示所有以41开头的ID号的人的全名和ID号
awk '$3~/^41/{print $1,$2,$3}' awk_test.txt | column -t
    # 默认情况下正则符^和$在awk中的作用与sed一样,都表示匹配一行的行首和行尾字符,但在awk中,这两个符号可以更加精准的匹配到某一列的行首字符和行尾字符
    # 例如此例中,'$3~/^41/'表示匹配第3列以41开头的内容,其中~用于连接两者的意义
awk '$3!~/^41/{print $1,$2,$3}' awk_test.txt | column -t    # 取反示例

# 示例:显示所有ID号最后一位数字时1或5的人的全名
awk '$3~/1$|5$/{print $1,$2}' awk_test.txt | column -t
awk '$3~/(1|5)$/{print $1,$2}' awk_test.txt    # 简化通用的$符
awk '$3~/[15]$/{print $1,$2}' awk_test.txt

# 示例:显示Xiaoyu的捐款,每个值前面都已$开头
gawk '/Xiaoyu/{gsub(/:/,"$",$4);print $1,$2,$4}' awk_test.txt
    # awk替换操作语法:'{gsub(/要替换的信息/,"替换成什么",要替换第几列信息)}'
    # echo oldboy | awk '{gsub(/oldboy/,"oleboy",$1);print $1}'    # 替换示例,gsub在awk中也是一个指令,所以gsub与print需要使用;号分隔

awk模式说明

普通模式

  • 正则表达式模式

    作为基本模式,最普遍的应用就是利用正则表达式作为匹配条件,搜索行内容

  • 比较表达式模式

    在一定范围内匹配条件。类似sed的行范围搜索

    awk 'NR>2{print $0}' awk.txt    # 从第3行开始匹配条件.$0表示显示所有列信息
    
  • 范围模式

    awk 'NR==1,NR==3{print $0}' awk.txt    # 具体范围显示
    

特殊模式

  • BEGIN模式:在文件处理前,执行相应操作;多用于测试、计算和修改内置变量
  • END模式:在文件处理后,执行相应操作;多用于计算、显示计算结果

特殊模式示例:

awk 'BEGIN{print "oldboy123"}NR>2{print $0}END{print "oleboy456"}' awk.txt | column -t    # 在过滤的文本内容前后都补充一行内容

补充:for循环语法

# for语法:for 变量 in 取值范围;do 执行指令;done
for i in {1..10}; do echo $i; done

awk运算方式

示例:计算/etc/services文件中的空行

grep -c "^$" /etc/services

累加运算(计数)

# 示例:计算文件空行信息
awk '/^$/{i=i+1; print $i}' /etc/services    # 未给i设置初始值时,默认等于0。i=i+1可以替换为i++
awk '/^$/{i=i+1; print i}' /etc/services    # 输出了累加过程

awk '/^$/{i=i+1}END{print i}' /etc/services    # 正确示例。通过END关键词仅输出累加结果

在awk中调用变量信息时,直接调用变量名不需要加$符;在awk中输出字符信息时必须带上双引号,否则会被识别为变量名

关于BEGIN的执行逻辑

awk '/^$/BEGIN{i=i+1; print $i}' /etc/services

语法错误,BEGIN模式是在文件处理前执行相应操作,语法上的重点在于文件处理前,将BEGIN关键词放在过滤符/^$/后面时,awk执行过滤时就表示已经读取了文件内容,读取文件内容后再执行BEGIN违背了BEGIN的语法规则

关于内置变量

什么是内置变量,例如

  • NR:行号信息
  • NF:尾列
  • FS:field separator,指定分隔符号,在awk中FS可以通过-F选项代替

示例:使用BEGIN修改内置变量

awk -F ":" '{print $1}' awk_test.txt    # 使用-F选项声明分隔符。等同于FS字段
awk 'BEGIN{FS=":"}{print $1}' awk_test.txt    # 使用BEGIN实现上例同样效果
awk -v FS=":" '{print $1}' awk_test.txt    # 实现上例同样效果

awk -v test=42 'END{print test}' awk_test.txt    # -v选项表示执行awk命令前,创建一个变量并赋值,awk命令可以直接调用此变量
    # 上例中如果不使用END关键字,则文件内容被读取多少行,就会输出多少次变量

求和运算(相加)

seq 10 | awk '{i=i+$0}END{print i}'    # 求1~10的和
seq 10 | awk '{i+=$0}END{print i}'    # 自加简化。要将第n列求和时,需要将$0修改为$n

awk数组概念

  1. 数组的组成说明

    数组名称[下标]

    awk 'BEGIN{h[110]="test"; h[111]="hebor"; print h[110],h[111]}'    #数组的组成与输出
    
  2. 利用数组累加运算

    示例:创建测试环境

    http://www.etiantian.org/index.html
    http://www.etiantian.org/1.html
    http://post.etiantian.org/index.html
    http://mp3.etiantian.org/index.html
    http://www.etiantian.org/3.html
    http://post.etiantian.org/2.html
    

    示例:www域名出现次数

    awk -F "[/.]+" '{array[$2]++; print array["www"]}' url.txt    # 输出累加过程。
    awk -F "[/.]+" '{array[$2]++}END{print array["www"]}' url.txt    # 输出累加结果
    
  3. 数组循环

    # 仍以上例为准,上例中环境条目较少,能够直观的看出只有www、mp3、post三种类型,假设无法直观看出有多少种数据类型时,就需要用到数组循环
    awk -F "[/.]+" '{array[$2]} END{for (i in array) print i}' url.txt    # for循环遍历数组中的值,并打印输出
    awk -F "[/.]+" '{array[$2]++} END{for (i in array) print array[i]}' url.txt    # for循环累加计算每种数据类型出现过多少次
    awk -F "[/.]+" '{array[$2]++} END{for (i in array) print i,array[i]}' url.txt    # 最终结果,一一对应
    
    # 过滤secure日志文件里的IP出现次数
    awk -F "(from)|(port)" '/Failed password/{array[$2]++} END{for (ip in array) print ip,array[ip]}' secure
    awk '/Failed password/{array[$(NF-3)]++} END{for (ip in array) print ip,array[ip]}' secure    # 另一种写法
    
  4. 数组排序

    # 查询登录失败的IP
    awk '/Failed password/{array[$(NF-3)]++} END{for (ip in array) print ip,array[ip]}' secure | column -t | sort -rnk 2
    
    # 查询登录失败使用的用户名
    awk '/Failed password/{array[$(NF-5)]++} END{for (name in array) print name,array[name]}' secure | column -t | sort -rnk 2
    ``
    
    示例:对web服务的`access.log`日志文件做数据统计
    
    ```shell
    # 1.每个ip地址的访问次数
    awk '{array[$1]++} END{for (ip in array) print ip,array[ip]}' access.log
    
    # 2.每个ip地址使用了多少流量
    
    
    # 3.每个ip地址访问的次数,同时统计每个ip地址使用了多少流量
    

    由于日志文件的信息记录不完整,此实例中仅展示统计方法

文件权限

缺乏安全性的系统不是完整的系统,系统中必须有一套能够保护文件免遭非授权用户浏览或修改的机制,Linux沿用了Unix文件权限的办法,即允许用户和组根据每个文件和目录的安全性设置来访问文件

用户管理

1.账户安全性

Linux安全系统的核心是用户账户,用户对系统中各种对象的访问权限也取决于登录系统时所使用的的账户,创建账户时会为每个账户分配一个唯一的UID,Linux系统会根据UID来跟踪对应的账户权限;Linux系统会为各种服务创建不同的账户,这些账户叫做系统账户,它们并不是真正的用户,是系统上运行的各种服务进程访问系统资源需要用到的特殊账户

  • 超级管理员账户:root,uid=0
  • 系统账户:用于管理进程信息,无法用于登录系统;Centos6中系统账户uid范围是1~499,Centos7的系统账户uid范围是1~999
  • 普通用户:相比较root用户,在权限上受到限制

著名的傀儡用户nobody,用于没有专门的虚拟用户管理的某些服务,又不想用root用户进行管理时,就会用到nobody

2.账户文件信息

/etc/passwd:保存用户相关信息
/etc/shadow:用户密码信息

hebor:$6$LAcHP3rS$JGDhJNTkERw0wRWIC9nJZlZqqN4HB960wxJuH5N1ThwnbB7veP5izyWd2nk6WVtQ5lFby0Xr24MEtjo0BUOEg/:19114:0:99999:7:::
用户名:加密密码:距离上一次修改密码的间隔时间:密码最短修改间隔:密码最长修改时限:密码过期提醒时间:密码过期宽限时间:账号失效时间:保留

在早期的Linux上,/etc/passwd文件中有加密后的用户密码,但鉴于很多程序都需要访问/etc/passwd文件获取用户信息,这成为了一个安全隐患,现在绝大多数Linux系统都将用户密码单独存放在/etc/shadow文件中,只有特定程序才能访问这个文件,而/etc/passwd文件中的密码栏则改为了x

示例:查看当前系统下支持的shell类型

more /etc/shells

3.用户相关目录信息

/etc/skel目录特征

  1. 此目录下的文件都是隐藏文件
  2. 此目录能够协助修复被破坏的家目录

/etc/skel目录文件解析

  • .bash_profile:环境变量和别名信息
  • .bashrc:系统默认的别名信息
  • .bash_logout:执行退出指令时,同时执行此文件内的命令信息。logout命令也可用于退出
  • .bash_history:此文件不存在于/etc/skel/目录下,存在用户家目录下,用于记录用户执行过的历史命令信息

/home/hebor/.bash_history文件并不会实时保存历史命令信息,最新的历史命令信息保存在内存中,.bash_history文件会定时读取内存中的命令信息并写入。history -w命令手动写入

/etc/skel/目录下保存了用户的初始环境变量文件。例如使用useradd hebor命令时,大致分为3个步骤:

  1. 创建用户 /etc/passwd

  2. 创建用户家目录

    cp /etc/skel/* /home/hebor/
    
  3. 修改用户家目录的数据权限信息

    chown -r hebor.hebor /home/hebor/
    

对以上步骤可以做个验证,在/etc/skel/目录下随意创建一个文本文件,然后新建一个用户,此刻新建的用户家目录下就存在之前新建的文本文件

示例:模拟用户提示符PS不正常

# 1. 先将PS注释
# 2. 使用普通用户删除所有数据 rm -rf /*,此步骤主要目的是删除该普通用户的家目录
# 3. 复制/etc/skel/目录下的所有文件到普通用户家目录
\rm -r /home/hebor    # 首先删除用户的旧家目录。直接cp /etc/skel/*目录下的文件可能会出现问题
cp -a /etc/skel/ /home/hebor/    # 直接将/etc/skel/目录复制并重命名。然后验证普通用户的提示符是否已正常
chown -R hebor.hebor /home/hebor/    # 修改用户家目录权限

4.用户管理命令

命令 描述
useradd 创建用户
userdel 删除用户
usermod 修改用户属性信息
passwd 修改已存在账户的密码
chpasswd 从文件中读取账户密码对,并更新密码
chage 修改密码过期日期
chfn 修改账户备注信息
chsh 修改账户默认登录shell
  1. 为了避免造成数据丢失,尽量不要删除用户,而是注释用户
  2. 默认不删除用户家目录和邮箱
  3. -r选项删除所有用户相关信息

删除用户时可能出现一种情况,此用户组被其他用户使用,那么删除此用户时就不会删除对应的用户组,而下次再重建同名用户时,系统会提示用户组已存在,新建用户失败。这种情况手动指定以下-g即可;userdel默认仅删除/etc/passwd文件中的账户信息,不会删除其他任何与用户相关的文件

useradd作为Linux系统用于添加新用户的主要工具,它使用系统的默认值和命令行参数来设置新账户属性信息,系统默认值被设置在/etc/defaut/useradd文件中,也可以通过useradd -D选项查看默认值;使用不同Linux发行版创建新用户时应先查看useradd的默认值,因为不同发行版的默认值不同,创建出来的新用户属性信息可能不符合管理员预期

GROUP=100   新用户会被添加到GID为100的公共组
HOME=/home  新用户的HOME将会位于/home/loginname
INACTIVE=-1 新用户账户密码过期后不会被禁用
EXPIRE= 新用户账户不设置过期日期
SHELL=/bin/sh   新用户账户使用sh作为默认shell
SKEL=/etc/skel  将此目录下内容复制到新用户的HOME目录下
CREATE_MAIL_SPOOL=no    不为新用户创建用于接收邮件的文件

密码管理

  1. 密码要复杂12位以上字母数字及特殊符号
  2. 保存密码信息:keepss-本地存储密码柜;lastpass-在线存储密码柜
  3. 用户和密码统一管理。linux下可以用openldap域统一管理,相当于AD域
  4. 动态口令

查看用户信息

  • id:查看用户信息
  • w:显示已登录系统的用户
  • uptime:查看系统性能
  • last:查看历史登录用户信息
  • lastlog:查询那些用户登录过系统
  • who

补充:审计服务概念

jumpserver:跳板机。统一管理用户信息、审计操作、监控远程操作信息

5.用户权限管理

用户权限设置方法:

  1. sudo命令
  2. 直接修改文件或目录的权限
  3. 将部分root的特殊能力赋予普通用户

sudo

sudo权限配置格式

root    ALL=(ALL)       ALL

# 源用户    可执行命令的主机=(目标权限用户)    可执行命令
hebor   ALL=(root)      /usr/bin/more /etc/shadow,/usr/bin/more /etc/gshadow

# 上述语法在最大程度上限制了普通用户的权限只能操作某些文件,另一种方式是直接将某些命令的权限给到普通用户
hebor   ALL=(root)      /usr/bin/more,/usr/bin/echo

# 涉及到比较多的命令权限的问题时,可使用通配符,表示将目标目录下的所有可执行命令的权限给到普通用户
hebor   ALL=(root)      /usr/bin/*

# 由于通配符的权限过大,为了限制普通用户的权限,排除部分命令的权限不给于普通用户
hebor   ALL=(root)      /usr/bin/*,!/usr/bin/vim

# 免密码执行sudo,在可执行命令区域的最前面加上NOPASSWD:
hebor   ALL=(root)      NOPASSWD: /usr/bin/more,/usr/bin/echo

sudo权限书写格式

  1. 可执行命令部分,必须使用命令的绝对路径
  2. 多个权限命令,用逗号或空格进行分隔
  3. 可执行命令部分,不能使用井号注释符

visudo命令的本质实际上就是修改/etc/sudoers文件,此文件也可以直接通过vim编辑,但建议使用visudo命令,因为此命令具备语法检查功能。或者手动编辑文件后,使用visudo -c检查/etc/sudoers文件的语法是否正确

sudo操作异常说明

hebor   ALL=(root)      /usr/bin/more /etc/shadow,/usr/bin/more /etc/gshadow,/usr/bin/echo 123 >> /etc/hosts
对于系统中默认存在的一些重要配置文件,sudo对普通用户是无法进行授权操作的。如果一定要使普通用户能够修改文件,那么只能修改文件本身的权限,例如此例中的/etc/hosts文件本身就属于普通用户无权操作的类型

修改visudo的默认编辑器

vim /etc/sudoers
Defaults    editor=/usr/bin/vim    # 添加此行

su

susudo 的区别:su 命令直接切换到目标用户,sudo 命令只是临时执行目标用户权限的部分命令

susu - 的区别:su 命令直接使用会对部分环境变量有影响,su - 命令会完全切换到目标用户的环境变量

文件权限

默认文件权限

umask命令用于设置所创建的文件和目录的默认权限,umask默认的值是0022,对文件来说全权限的值是666、对目录来说全权限的值是777,那么创建文件或目录时,umask的值会作为掩码,例如创建一个文件,默认权限是666,666减去umask的掩码值022后,就变成了644;通常umask的值会设置在/etc/profile文件中,也有一些发行版会将umask值设置在/etc/login.defs文件中,通过umask命令也可以直接修改默认值

chown指定更改文件所属用户和所属组时,如果要修改所属组,但又未指定所属组名,例如chown hebor. /etc/hosts,此时会判定所属组修改为目标用户的主属组

特殊权限

  • SUID:当文件被用户使用时,程序会以文件属主的权限运行
  • SGID:对文件而言,程序会以属组的权限运行;对目录而言,目录下新建的文件会以目录的属组作为文件的默认属组
  • Sticky(粘着位):进程结束后文件还驻留(粘着)在内存中

Sticky针对其他用户的权限位修改,若其他用户权限位有执行权限,则显示为t、若其他用户权限位无执行权限,则显示为T;共享目录在Sticky权限下,普通用户创建的文件或目录,只能被该用户或root用户修改,其他用户无法删除该用户创建的文件或目录

文件权限

  1. 当文件权限为000时,所属用户看起来是没有写权限的,但可以通过vim写入后强制保存,也是能够成功保存的
  2. 当文件权限为400时,通过vim的强制写入,可以达到读写成功的效果
  3. 当文件权限为200时,所属用户的每一次写入都是覆盖写入。但因为写入权限,可以通过追加重定向的方式添加写入
  4. 当文件权限为100时,所属用户仍无法执行文件,需要搭配读权限,每一次写入都是覆盖写入

目录权限

  1. 当目录权限为000时,所属用户无任何权限,无法执行任何操作
  2. 当目录权限为400时,所属用户能显示目录下的文件名,但无法显示文件的属性信息
  3. 当目录权限为200时,所属用户无任何权限,无法执行任何操作
  4. 当目录权限为100时,所属用户仅能够进入目录
  5. 当目录权限为500时,所属用户能显示目录下的文件详细信息
  6. 当目录权限为300时,所属用户具备新建和删除权限

一个目录如果要能够正常查看 和 正常修改数据,都需要执行权限配合。以上条件对root用户皆不生效,能够限制root用户的权限只有执行权限

使用less命令或vim命令能够直接查看到目录block保存的数据

less shell/    # 对目录执行less命令
vim shell/
posted @ 2023-06-19 09:15  hebo  阅读(57)  评论(0)    收藏  举报