面试自闭了, 回顾一下基础
准备
靶场:
不足的地方再补充
手册:
--help
- tldr
- man
shell基础
shebang
shell脚本的语法还是要了解的, 这有利于我们写脚本
一般我们会在第一行写一个Shebang
, 也就是声明用的环境
#!/bin/bash |
这样的话在运行的时候可以不加前面的解释器, 直接运行
./shell.sh |
变量
一般来说空格不应该影响语言的实现, 但是在shell脚本中, 空格有时候会有些特殊的含义, 所以能不加还是不加吧
变量声明如下
i=0 |
声明后通过加$
来引用
echo $i |
获取输入的话用的是read
read num |
循环
可以参考 https://www.cnblogs.com/EasonJim/p/8315939.html
一般来说我们循环都是通过
while/for 循环语句 |
这样的方式来编写, 例如for语句
for((i=1;i<=10;i++)); |
在shell脚本中, ;
可以写也可以不写, 如果一行一个语句的话是没有区别的
for语句的地方需要用(())
来包裹, 不然会报错
正确执行如下
另一种写法如下
for i in {1..10} |
道理相同, while的话如下
while ((i<10)) |
可以看到这里也是用了(())
进行包裹, 在shell中, (())
一般用来表示数学运算, 如果不加的话直接会报错
其中((i++))
等价于i=$((i+1))
if-else
大概的语法是
if [ express ] |
注意[ express ]
的空格必须保留
关于[]
和[[]]
的区别可以阅读 https://stackoverflow.com/questions/669452/is-double-square-brackets-preferable-over-single-square-brackets-in-ba
从简单的使用来说, [[]]
会方便一些
数组
数组的声明方式如下
array=(A B "C" D) |
或者直接用
array[0]="A" |
来声明, 这个用法差不多, 比较有趣的特性是
获取数组所有元素
${array[@]} |
获取数组长度
${#array[@]} |
对数组元素进行处理
${array[@]/[a-z]/* |
题目
输出1到50
#!/bin/bash
for i in {1..50}
do
echo $i
done计算两个数的加减乘除
#!/bin/bash
read x
read y
echo `expr $x + $y`
echo `expr $x - $y`
echo `expr $x \* $y`
echo `expr $x / $y`这里使用了
read
读取输入, 然后通过expr
计算表达式并输出比较两个数的大小并输出相应的值
#!/bin/bash
read x;
read y;
[[ $x -gt $y ]] && echo "X is greater than Y";
[[ $x -eq $y ]] && echo "X is equal to Y";
[[ $x -lt $y ]] && echo "X is less than Y";这里用了
&&
来进行输出, 很好懂输入
y|Y
则输出YES
, 反之输出NO
#!/bin/bash
read choice
if [[ $choice == "Y" || $choice == "y" ]]
then
echo "YES"
else
echo "NO"
fi计算表达式并输出3位小数
#!/bin/bash
read exp
printf "%.3f" $(echo $exp|bc -l)这里使用了
printf
进行格式化输出, 和c是一样的, 另外用bc
进行运算,-l
表示设置小数位数为20
, 也可以设置其他的值计算平均数
#!/bin/bash
read n
exp=0
for(( i=0;i<$n;i++ ))
do
read x
exp=$((exp + x))
done
printf "%.3f" $(echo "$exp/$n"|bc -l)读入数组并输出3:5的部分
#!/bin/bash
i=0
while read line
do
words[$i]=$line
i=$((i+1))
done
echo ${words[@]:3:5}更简洁的做法是
#!/bin/bash
words=$(cat)
echo ${words[@]:3:5}通过
cat
获取输入, 然后输出切片即可过滤数组中含有
a|A
的元素#!/bin/bash
words=$(cat)
echo ${words[@]/*[aA]*/}他的语法是
使用b替换a, 只替换第一个
/a/b
全部替换
//a/b
有点像正则, 但是又有些不太一样
取出下标为3的元素
#!/bin/bash
words=$(cat)
echo ${words[3]}输出元素个数
#!/bin/bash
words=$(cat)
echo ${#words[@]}
替换首字母为
*
#!/bin/bash
words=$(cat)
echo ${words[@]/[A-Z]/.}输出没有重复的值
read
words=$(cat)
words2=${words[@]// /^}
echo $((words2))做法很巧妙, 由于输入的值只有一个是不重复的, 我们可以使用异或来求值
首先读取输入为数组
words=$(cat)
然后展开为字符串, 并替换空格为
^
words2=${words[@]// /^}
然后计算表达式, 输出
echo $((words2))
简单的脚本编写就到这里了, linux的灵魂还是在于命令的组合
文本处理
知道就行, 这一块其实用的不多, 比较重要的是后面的sed
, awk
, grep
cut
主要参数如下
-b
: 按bytes选取-c
: 按char选取-f
: 按列选取-d
: 设置分隔符, 如-d';'
选取的范围语法
从题目来进行了解
题目
假定题目内容使用管道传入, 其他命令相同, 相关的输入内容可以到靶场查看,
选取第3个字符
cut -c3
选取第2和第7个字符(超出则不显示, 不报错)
cut -c2,7
选取第2到第7个字符
cut -c2-7
选取到第4个字符
cut -c-4
选取前3列, 按
\t
分隔cut -f-3
选取第4列, 按
分隔
cut -d' ' -f4
head & tail
这两个命令相近, 放在一起
head语法如下
tail语法如下
主要参数如下
-c
: 按bytes选取-n
: 按行选取-f
: 监视文件更新(tail)
题目
选取前20行
head -n 20
选取前20个字节
head -c 20
选取后20行
tail -n 20
选取第12行到22行
head -n 22|tail -n +12
这里的
+12
(tail)的意思是, 打印从12行开始的所有行, 同样我们可以使用-12
(head)表示打印从最后的12行往前的所有行
tr
用于替换
其中特殊的pattern如下
题目
替换
()
为[]
tr "()" "[]"
删除所有小写字母
tr -d "[:lower:]"
替换多个空格为单个空格
tr -s "[:space:]"
sort
主要的参数如下
-d
: 字典序-n
: 数字排序-r
: 逆序(默认为升序)-f
: 大小写忽略(视为大写)-t
: 列的分隔符
题目
按字典序升序
sort
按数字降序
sort -rn
按第二列升序排序, 分隔符为
sort -k2,2 -t' '
这里需要注意的是,
-k2,2
表示按第二列排序(如果不加,2
在相同的时候会按照后面的列排序),-t''
则是表示按空格分隔按第二列升序排序, 分隔符为
\t
sort -k2,2 -t$'\t'
这里不同的地方在于, 分隔符为特殊符号, 我们需要让他解释为
TAB
, 而不是\
+t
, 因此这里需要加一个$
, 并且不能使用双引号, 具体的原因可能是底层实现的解释有关吧, 一般来说我们使用"
包裹都应该可以转义的
uniq
直接看题目吧
题目
输出重复的值
uniq
输出每个值及重复次数(包括1次)
uniq -c|xargs -L1
我们看一下
xargs
和不加的区别其实就是消除前面的空格而已
忽略大小写输出重复的值和重复次数
uniq -c -i|xargs -L1
输出不重复的值
uniq -u
paste
我觉得也是替换
题目
将多行内容合并为一行, 用分号分隔
paste -sd';'
这样的看的话应该比较好理解
用
-s
将多行合并为一行, 然后用-d
标志分隔符将多行内容每三行合并为一行, 用分号分隔
paste -sd';;\n'
这个命令简直神奇, 他的分隔符是可以迭代的, 这里传入了三个, 那么依次为
;
,;
,\n
作为分隔符, 十分神奇
sed, grep, awk
这三个号称三剑客, 在linux的命令中占有相当大的比重, 需要重点关注
grep
这个应该都用过了, 先拿来讲讲
部分参数如下
比如我们要搜索一个字符串, 最简单的就是
grep cat |
如果是带有符号的, 需要加上"
如果想要输出行号, 加上-n
要输出前后的行呢
想要找到没有匹配的, 加上-v
想要忽略大小写, 就加上-i
, 想用正则, 就加上-e
….
这个命令的使用场景很多, 可以参考 https://linux.cn/article-5453-1.html
我们先看一下题目吧
题目
找出所有含有
the
的行grep -w "the"
按词匹配
找出所有含有
the|those|that
的行grep -Ew "th(e|at|en|ose)"
使用
E
表示拓展的正则表达式, 用起来比较方便, 如果是e
的话, 一些特殊符号需要加上\
转义具体的区别可以看 https://stackoverflow.com/questions/17130299/whats-the-difference-between-grep-e-and-grep-e
忽略大小写找出含有
the
的行grep -wi "the"
找出没有
the
的行grep -vw "the"
找出信用卡号码存在相邻位相同的卡号, 例如
1234 4567
中的4 4
grep -e "\([0-9]\)\s*\1"
这里用的是
-e
来进行正则匹配, 其中\1
表示第一个块(括号)匹配到的项, 在这里是([0-9])
这个块,\s
表示空格或\t
或\n
,*
表示0或多次, 假设括号匹配到1, 那么\1
就是4
当然我们常用的还有-r
递归查找的, 例如
grep -rin flag |
则表示在当前目录递归查找flag
字符串
另一个比赛常用的是
find / -iname "*flag*" |
这个查找的是文件名, 另外还有一个更快的查找文件内容的是ag
, 不过不在本文范围内, 感兴趣的可以了解一下, 真的快很多, awd可能有奇效
sed
sed命令主要是用来进行替换字符串, 当然功能比前面提到的要强大很多
更详细的介绍可以参考 https://coolshell.cn/articles/9104.html, 下面会引用部分内容
看一下相关的使用场景吧
s命令
假设我们现在存在一个文件input.txt
From fairest creatures we desire increase, |
将其中的the
全部替换为this
sed "s/\bthe\b/this/" |
根据手册, 如果没有提供-e, -f等参数, 则会默认使用第一个不是
-
开头的参数作为script(也就是相当于前面加上了-e)
首先是替换的格式, 将每行的首个a替换成b,
sed "s/a/b/" |
全部替换的是
sed "s/a/b/g" |
而\b
表示的是单词边界, 他是零宽的, 也就是不会实际匹配任何字符, 但是会识别任意的单词分隔, 在一些边界更准确, 如果只是
sed "s/the/this/" |
会使得如then
也被替换
如果是
sed "s/\sthe\s/this/" |
由于\s
不是零宽的, 也会匹配上, 在替换的时候就会有些问题
和vim类似, 我们需要匹配某几行的时候, 可以用
3,5s/a/b/3 |
这样的方法来匹配第3行到第5行中的每一行的第3个a
假设我们要去掉一段html文本中的内容
<b>This</b> is what <span style="text-decoration: underline;">I</span> meant. Understand? |
我们如果
sed "s/<.*>//g" |
是因为正则是默认贪婪匹配的, 那么我们可以这样
sed "s/<[^>]*>//g" |
试了一下加上?
不能实现非贪婪匹配, 查了资料需要用其他的方法, 有兴趣的可以去看看
而这里的正则和其他的正则一样, 都可以用\num
来获取前面括号的匹配内容, 比如下面的例子
sed 's/This is my \([^,&]*\),.*is \(.*\)/\1:\2/g' |
我们拆分一下, 首先第一个括号是
([^,&]*) |
表示匹配除了,
和&
之外的任意多次的内容
第二个是
(.*) |
匹配任意内容
假设文本为
This is my cat, my cat is betty |
那么匹配为
This is my (cat), my cat is (betty) |
N命令
将下一行的内容纳入缓冲区一起匹配
也就是原本匹配一行变成了两行一起匹配
看下面的例子, 原本
sed "s/my/your/" |
会替换每一行的第一个my
, 而加上`N”后
偶数行的部分没有被替换, 那如何每两行连接在一起呢
sed "N;s/\n//" |
a命令和i命令
和vim很像, a表示追加(append), i表示插入(insert)
sed "1 i line1" |
表示在第一行前面插入
sed "$ a line last" |
表示在最后一行后面追加
c命令
替换匹配行
可以用
sed "1 c line1" |
或者
sed "/fish/c line1" |
d命令
删除匹配行
使用方式类似
sed "1 d" |
sed "/fish/d" |
p命令
这是用来打印的命令, 如果匹配到就会打印多一次
加上-n
则只会输出匹配到的行 (grep)
如果要全文本打印的话(cat)
sed -n "p" |
写入文件
上面的操作都是在STDOUT
中操作的, 对文件内容没有修改, 如果需要修改, 可以加上-i
参数
others
对于地址的匹配模式为
[start[,end]][!]{cmd} |
其中start
和end
都可以使用模式匹配或直接声明数字, 比如
1,3 |
!
表示匹配到是否执行后面的cmd
, 如果加上!
则不执行
而cmd
可以嵌套
# 单条命令 |
还有一个是关于hold spaces
和pattern space
的介绍, 图很形象, 这里就直接搬运了
题目
将每行第一个
the
换成this
sed "s/\bthe\b/this/"
将所有的
thy
换成they
sed "s/\bthe\b/this/g"
将所有的
thy
用{}
包裹起来, 忽略大小写sed "s/thy/{&}/Ig"
其中
&
表示前面匹配到的内容将每行的前三组数字替换成
****
sed -E "s/[0-9]{4} /**** /g"
将每行的四组数字逆序输出
sed -E "s/(.+) (.+) (.+) (....)/\4 \3 \2 \1/"
其中
\num
表示前面第几组括号的匹配内容下面开始是网上收集的一些题目
打印文件的所有行
sed -n "p"
将每行第一个数字移动到行末尾
sed -r "s/([0-9])(.+)$/\2\1"
逆序输出
sed "1!G;h;$!d"
输出偶数行
sed -n "n;p"
更多高级用法可以参考 http://www.178linux.com/96587
再深入就不好记了, 不如到生活中去学习吧
awk
不知道是不是我装的版本问题…他竟然没有--help
, 直接敲awk
即可看到帮助
这个看起来似乎少了很多选项, 但是它的功能也十分强大, 毕竟是能用来提权的命令
主要用于提取文本和格式化输出
同样是参考自 https://coolshell.cn/articles/9070.html, 实验文本最后两行做了一点小修改
Proto Recv-Q Send-Q Local-Address Foreign-Address State |
首先看一下基本的格式
awk '{print $1,$5}' |
注意列数的计数从1开始, 默认按空格或\t
分隔, 使用{}
包裹命令, 然后$num
代表列数, $0
则是整行
缺少的列数则为空, 还有就是一般用'
单引号包裹, 避免$
被shell解析
假设我们要筛选只有4列的行
awk '!$5 {print $0}' |
我们可以用-F
来指定分隔符, 加上[]
可以指定多个
awk -F'[:]' '{print $1}' |
输出的分隔符可以使用OFS
指定
awk -F'[:]' '{print $1,$2}' OFS=":" |
内部的预定义变量为
$0 | 当前记录(这个变量中存放着整个行的内容) |
---|---|
$1~$n | 当前记录的第n个字段,字段间由FS分隔 |
FS | 输入字段分隔符 默认是空格或Tab |
NF | 当前记录中的字段个数,就是有多少列 |
NR | 已经读出的记录数,就是行号,从1开始,如果有多个文件话,这个值也是不断累加中。 |
FNR | 当前记录数,与NR不同的是,这个值会是各个文件自己的行号 |
RS | 输入的记录分隔符, 默认为换行符 |
OFS | 输出字段分隔符, 默认也是空格 |
ORS | 输出的记录分隔符,默认为换行符 |
FILENAME | 当前输入文件的名字 |
如果想当成grep用的话, 可以这样
awk '/LISTEN/' |
对某一列进行选取
awk '$6 ~ /LISTEN/' |
~
表示模式的开始, 前面是匹配的项, 也就是第六列, 取反的话加上一个!
awk '$6 !~ /LISTEN/' |
如果选择多个的话我们可以使用|
等逻辑运算符
如果想用awk来进行一些运算, 例如计算总行数
awk '{sum+=1} END{print sum}' |
这里利用的是END
语句来进行最后的输出, 而不是在每行处理的时候进行输出
或者利用内建变量
awk 'END{print NR}' |
当然最简洁的是
wc -l |
而且awk还能使用if-else这类的语句
awk '{if($6=="FIN_WAIT2")print "FIN";else print "OTHERS"}' |
我们可以当成一个脚本解释器来运用他, 因为他甚至还有循环和数组语句, 十分可怕
体会一下逆序输出
awk '{line[NR]=$0} END{for(i=NR;i>0;i--) print line[i]}' |
在脚本比较复杂的时候, 我们可以使用文件加载脚本(前面的sed也有这样的功能)
{ |
题目
打印只有两项成绩的学生名
awk '!$4 {print $1}'
如果有一项成绩低于50, 则标记为
FAIL
, 否则标记为PASS
awk '{print $1,":", ($2<50||$3<50||$4<50) ? "FAIL" : "PASS"}'
这是是用了三目表达式, 和c是一样的, 另外用
,
分隔的项, 输出会自动加上空格分隔
根据平均分标记不同的等级
awk '{avg=($2+$3+$4)/3;print $1,":", (avg>=80)?"A":(avg>=60)?"B":(avg>=50)?"C":"FAIL"}'
每两行合并输出
awk '{if(NR%2) printf "%s;" $0;else print $0}'
打印第一列和最后一列, 使用
@
作为分隔符awk OFS="@" '{print $1,$NF}'
将第二列相加求和
awk '{sum+=$2} END{print "sum", sum}'
打印20行以后的行
awk 'NR>20{print $0}'
后记
文章就先到这里了, 其实这些命令还有很多东西, 想了解的可以多看看教程和man手册
可能有朋友会问, 为什么要用这些很旧的工具呢? 明明有那么多图形界面的工具, 我用vscode不香吗?
香是肯定香的, 但是一般我们在服务器场景中, 没有图形界面, 只能用这些命令, 而且在大数据的处理中(海量日志), 这些命令的速度要比你下载下来, 然后编辑器打开, ctrl+f/h快很多的, 还能节省很多网络传输的时间
wireshark做的这么好, 不还是有tshark吗XD
参考链接
- https://www.hackerrank.com/
- https://coolshell.cn/articles/9104.html
- https://coolshell.cn/articles/9070.html
- https://my.oschina.net/u/3771583/blog/1632823
作者: cjm00n
地址: https://cjm00n.top/Linux/linux-command-learning.html
版权声明: 除特别说明外,所有文章均采用 CC BY 4.0 许可协议,转载请先取得同意。
评论