邂逅 shell in Linux

脚本

  • 脚本开头加 #!/bin/bash 指定运行的解析器,不是注释
  • chmod u+x filename 加执行权限,如果不加权限可以 bash filename执行脚本
  • 退出脚本 exit NUM 代表返回值 可以$?获取

特殊符号

~ 家目录 cd ~
! 执行历史命令 history 查看历史命令 !5 !! 执行上一条命令
$ 变量取内容符 $? 非0表示有问题 0表示没有问题
& 后台执行
* 匹配所有
? 匹配除回车以外的一个字符
; 命令之间用;分隔
| 管道
\ 转义字符
`` 命令中执行命令 echo "today is `date +%F`"
'' 字符串 但不解释变量 '$a' # $a  'Hello World !'
"" 解释变量 "$a" # 1 "Hello World \!" 如果包含特殊字符 需要加转义符\

特殊变量

IFS 内部字段分隔符 默认空白字符(换行符,制表符,空格)
$*:代表所有参数 间隔为IFS内定参数第一个字元 所有参数视为单个实体 "$1c$2c$3"
$0: 脚本的文件名
$@:与*类似,不参考IFS 以列表方式打印所有参数 "$1" "$2" "$3"
$#:参数数量 # length=${#var} 获取变量的长度
# echo $# | tr ' ' '\n' > file # 将空格转换为回车写入文件 
$:执行上一条命令返回值
$-:执行的foreground pipeline的选项参数
$$: 本身的processid
# /tmp/cpu_usage.$$ $$为当前脚本的进程id 如果是1345,文件名就是cpu_usage.1345
$!:保存上一个命令的PID
$N:shell的第几个外传参数
shift 参数左移¥
for i in `seq 1 $#`
do
echo $i is $1
shift
done  # 1 is a 2 is b 3 is c
# ./img_download.sh url -d images
while [ $# -gt 0 ]
do
  case $1 in
  -d) shift; directory=$1; shift ;;
  *) url=$1; shift ;;
  esac
done

重定向

> 重定向输入 覆盖原数据
>> 重定向追加输入,在原数据末尾添加
< 重定向输出 wc -l < /etc/passwd
<< 重定向追加输出 fdisk /dev/sdb << EOF ... EOF
# 向文件输入内容
cat < < EOF > test.sh # end of file
123
1231231
EOF
# 通过STDIN提供多行输入 出现在EOF之间的文本都会作为标准输入传给mysql
mysql -u $user -P$pass STUDENTS <<EOF 2>/dev/null
CREATE TABLE students(
id int,
name varchar(100),
);
EOF
# 写成多行用EOF
depts="`mysql ... students<<EOF
set @I:=0;
...;
`"

运算

let result=no1+no2
result=$[ no1 + no2 ]
result=$((no1+5))
result=`expr 3 + 4`
# expr 整数运算 要加空格
expr 1 + 1
expr 5 - 2
expr 5 \* 2 需要转义
expr 5 / 2 # 2 整数
# bc计算器可以保留小数
echo "scale=2;100/3" | bc # 33.33
hours=$(echo "$minutes / 60.0" | bc)
# (()) 双小圆括号
echo $((100**3))
# 将命令的输出赋给变量, 两种方式
cmd=$(COMMANDS)
cmd=`COMMANDS`
utime=$(ssh $(USER)@$(IP) uptime | awk '{ print $3 }') # 将结果保存在变量中
echo $utime
cur_date=$(date +%D)
# 赋值
NEEDED_INPUT_FILES="${NEEDED_INPUT_FILES} symaccess_list_view_detail symaccess_list_logins"
# 计算
lun_capacity=$(echo "${lun_capacity_array[$____index]}" | awk '{print int($0)/2/1024/1024}')

执行命令

(命令1;命令2;命令3) # 开启一个子shell环境来执行括号里的内容
{ 命令1;命令2 } # 将中括号的命令放在现成的shell执行
{
  if [ "${LOCAL_SCRIPT_PARAM_VMAX_SID}" = "all" ]
  then
    ... |
    grep -v "^#" |
    awk '{ print $1 }'
    ...
} | \
while read VMAX_SID VMAX_NAME
do
  (( ii+=1 ))
  A_VMAX_NAME[$ii]="${VMAX_NAME}" # 将VMAX名字,SID存入列表中
  A_VMAX_SID[$ii]="${VMAX_SID}
done

输入输出

eval
变量的嵌套替换
var1="hello"
i=1
what=var${i}
temp=what
echo $temp # what
eval temp=$(echo \$$what) # $what=var1->echo $var1="hello" eval会将表达式中以$开头的所有变量进行整体替换
versions="(2 4)" # 字符串 eval versions="(2 4)" 数组 eval会把赋值语句中双引号之间的变量直接付给=前变量
eval $(printf "a=\"1234\" b=\"4556\"")
echo $a # 1234 会把标准输出中的内容进行赋值
eval echo \$$# 获取脚本中的最后一个参数
模拟指针
x=100
ptrx=x
eval echo \$$prtx # 100
eval $ptrx=50
exho $x # 50
# 环境变量添加
prepend() {[ -d "$2" ] && eval $1=\"$2\$\{$1:+''\$$1\} && export $1 ; }  # ${parameter:+expression} 当parameter有值,则使用expression的值
echo
echo [-ne][字符串]
-n 不要自动换行
-e 搭配以下使用
    \a 发出警告声
    \b 删除前一个字符
    \t 插入tab
    \n 换行且光标移到行首
    \f 换行光标在原位置
    \c 不加换行符
echo -e "\33[字背景颜色;文字颜色m字符串\33[0m"
echo -e "\033[41;33m红底黄字\33[0m"
${command} # 直接运行command命令
文本切片 ${ \ \ }
echo ${var/line/REPLACED} # 将字符串中的Line替换为replaced
pusg=${pusg/\%/} # pusg中去掉%
echo ${string:4} # 打印字符串第5个字符及以后的字符
echo ${string:4:8} # 从第5个开始,打印8个字符
${string:(-1)} # 最后一个字符
${string:(-2):2} # 打印最后两个字符
# #
${变量#关键词} 将从头到尾满足关键词的最短匹配删除
${变量##关键词} 将从头到尾满足关键词的最长匹配删除
a="/home/stmrep/STMREP/collect/20200330-2031_VMAX8_linuxa"
echo ${a#*_} # VMAX8_linuxa
# %
${变量%关键词} 将从尾到头满足关键词的最短匹配删除
${变量%%关键词} 将从尾到头满足关键词的最长匹配删除
a="123.456"
echo ${a%\.*} 123  echo ${fc_name%\.*} 截取最后一个.之前的部分
# /
${变量/old/new} 将满足关键词的第一个old变为new
${变量//old/new} 将满足关键词的所有old变为new
printf 格式化字符串
printf "%-5s %-10s %-4s\n" No Name Mark # No  Name  Mark -表示左对齐,不指名则右对齐 .2保留两位小数 要加换行符\n
printf "%-5s %-10s %-4.2s\n" 1 wang 30.555 # 1  wang  30.55
printf "%-8s %-14s...\n" \
"date" "ip" "device" ... > $logfile # 将格式化标题写入日志文件
read
  • 接受键盘的输入,回车表示结束
-p 打印信息
read -p "Enter: " var
-t 指定超时时间
-s 输入的不回显
-n 只读取前n个输入的字符
read -n 2 var 从输入中读取2个字符给var变量
-d 特定的定界符作为输入行结束
read -d ":" var # 当输入hello: var=hello

变量

  • 内存在系统启动的时候按照1B为单位划分为若干块,进行统一16进制编号,对内存使用情况做记录,保存在内存跟踪表。

字节(1B) 字(2B) 块(512B) 千字节(1024B) 兆字节(1024KB) 吉字节(1024MB)

1GB = 1024 (MB) * 1024(KB) * 1024(B) * 8(bit)

内存占用:字符占一个字节(1B),字符串时字符串的长度加1字节长度(\0特殊字符,代表字符串结束)

SER = "ABC" 逻辑地址 对应 物理地址0X5...0X8

在建立变量时计算机将逻辑地址(变量名)和物理地址做对应

读数据时计算机根据物理地址找到逻辑地址,从对应的内存地址上读出数据返回

  • 读取变量:echo $变量名
  • 取消变量:unset name 全局变量:export name="wang"
  • 本地变量:用户私有变量,只有本用户才能用,保存在bash_profile,.bashrc
  • 全局变量:所有用户都可以使用,保存在/etc/profile,/etc/bahrc
  • 变量是否用引号括起来
"双引号" 希望将文本视为字符串而不是正则表达式 禁止分词和全局搜索的上下文
'单引号' 字符串中禁止插值和反斜杠的特殊处理
无引号 希望分词和全局
words="hello world" for word in $words hello world(打印两次) , in '$words' $words, in "$words" hello world(打印一次) 
pattern='file*.txt' ls $pattern (正确搜索) ls '$pattern' 搜索 $pattern ls "$pattern" 搜索file*.txt
echo "we have $count ${fruit}(s)" # 如果和其他字符串连接,需要加上{}

数组

基本数组
  • 定义:数组名=(元素1,元素1.。。。)
 array=(tom wang allen) 
 (`cat /etc/passwd`) # 将读出的每一行作为一个元素
 (tom "bash shell")
 # 不需要预先定义
 array[0]='tom'
 # 查看数组
 declare -a
 # 指定分隔符
 oldIFS=$IFS
 IFS=,
 values=($line) # 以逗号为分隔符将line元素存入数组 1,wang p,98,cs
 values[1]="\"`echo ${values[1]} | tr ' ' '#' `\""
 SNAP_CG_NAME_ARRAY=(${NAME_ARRAY[@]}) # 数组变量的传递
 src_lun_id_arr=($(echo $src_lun_id_str | tr ',' ' '))  # 字符串变数组
  • 读取:${数组名[索引]}
echo ${array[0]} 单个元素
echo ${array[@]} 访问所有元素 <=> echo ${array[*]}
echo ${#array[@]} 统计元素个数
echo $[!array[@]} 打出所有元素索引
echo ${array[@]:1} 从下标1开始打印元素
echo ${array[@]:1:2} 从下标1开始访问两个元素
SNAP_CG_NAME_ARRAY=(${NAME_ARRAY[@]}) 数组变量的传递 注意:这种方式会丢失索引 从0开始
关联数组
  • 定义:declare -A ass_array1
  ass_array([index1]=val1 [index2]=val2)
  ass_array[index1]=val1

逻辑比较

[ condition ] && action; # condition为真, 执行action
[ condition ] || action; # consition为假, 执行action
    [[ $FAIL_CNT_19 -eq 0 ]] || return 10 第一个是真就不会再往右执行
    [[ `grep -c 'Symmetrix ID' $NEEDED_F` -ne 1 ||
    `grep -c 'Fibre' $NEEDED_F` -lt 1 ]] && (( VMAX8_ERROR+=1 ))
    # check params
    #
    [[ $# -eq 4 ]] || return 1 左边不为真就会直接返回

流程控制

子shell
# 运行在子shell中
(
for ...
) >> $logfile
() | sort -nrk 4 | awk '{ printf("%-4s %s\n", NR, $0) }'
if
  • 数学比较
-eq 等于
-gt 大于
-lt 小于
-ge 大于或等于
-le 小于或等于
-ne 不等于
  • 字符串比较
== 等于 [[ $str1 == $str2 ]] # 字符串比较用双中括号
!= 不等于
-n 检查字符串长度是否大于0
-z 检查字符串长度是否为0 检查空字符串
  • 文件比较
-d 文件存在且为目录
-e 检查文件存在
-f 文件存在且为文件
-r 检查文件存在且可读
-s 检查文件存在且不为空
-w 文件存在且可写
-x 文件存在且可执行
-O 文件存在且被当前用户拥有
-G 文件存在且默认组为当前用户组
file1 -nt file2 1是否比2新
file1 -ot file2 1是否比2旧
  • 其他
&& 与 || ! = 赋值
[ $? -eq 0 ] && echo success || echo fail
-a AND &&
-o OR ||
! NOT !
  • 语法
if [ condition 1 ]; then
    commands
elif [condition 2 ]; then
    commands
else
    commands
fi
高级用法
for ((;;))
    do
        echo "hehe"
done

if [[ "$var" == r* ]]
    echo "$var"
fi
双方括号可以在里面使用通配符

if [[ $param_value == ""|| $param_value =~ "description" ]] # 包含
then
fi

if [[ "${host_id[$index_host]}" =~ "(" ]] # 包含
for
  • 语法
1
for var in value1 value2
do
    commands
done
2 C形式
for ((i=1;i<10;i++))
do
    echo $i
done
continue: 跳过某次循环
break:结束循环
break N:跳出某个循环 从内到外依次为1,2,。。。,N
break 2: 跳出外层循环
oldIFS=$IFS
IFS=":"
count=0
for item in $line
do
  [ $count -eq 0 ] && user=$item  # [] 里直接可以判断
  [ $count -eq 6 ] && shell=$item
  let count++
done
IFS=$OldIFS
echo $user's shell is $shell
# 遍历序列
echo {1..50} {a..z}
# 建立txt
for name in {1..100}.txt
do
  touch $name
done
while
  • 语法
while [ condition ] # 条件为真 继续循环
do 
    commands
done

IFS=$":"
while read f1 f2 f3 f4 f5 f6 f7
do
    echo "$f1 $f2 $f3"
done < /etc/passwd

while read filename;
do
  echo downloading $filename
done < /tmp/$$.list
util
until [ condition ] # 条件为假,继续循环
do
    commands
done
case
read -p "NUMS: " N
case $N in
1) echo haha ;;
2) echo hehe ;;
3) echo heihei ;;
*) echo bye ;;
esac
# sqlite数据库
case $1 in
init) cmd="CREATE TABLE address (name string, phone string, email string);" ;;
query) cmd="SELECT name, phone, email FROM address WHERE $2 LIKE '$3';" ;;
insert) cmd="INSERT INTO address (name, phone, email) VALUES('$2', '$3', '$4');" ;;
esac
echo $cmd | sqlite3 $HOME/addr.db | sed 's/|/\n/g' # sqlite 执行命令并初始化输出

while [ $# -ne 0 ]
do
    _OPT="$1" ; shift
    f_stm_scripts_log_debug_message "$STM_SCRIPTS_RUN_CALLER_BASE: checking option $_OPT (= $1) "
    case $_OPT in
        "-d")       LOCAL_SCRIPT_PARAM_DEBUG="on"
                    export STM_GLOBAL_DEBUG="on"
                    ;;
        "-e")       if [ "${1}tIp" != "tIp" ]
                    then
                        LOCAL_SCRIPT_PARAM_ENV="${1}" ; shift
                    fi
                    ;;
        *)          f_local_print_usage
                    f_stm_scripts_log_local_message "F" "invalid script call '$STM_SCRIPTS_RUN_CMD' "
                    f_stm_scripts_exit_script 1
                    ;;
    esac
done

函数

  • 语法
1.
函数名() {
    代码块
    return N
}
2.
function 函数名() {
    代码块
    return N
}
repeat(){ while :; do $@ && return; sleep 30; done } # 当脚本传入的参数可执行就返回 否则死循环
# 文本文件数据库
Joe User~123 Example Street
function addr {
  grep $1 $HOME/etc/addr.txt | sed 's/~/\n/g'
}
addr Joe
# Joe User
# 123 Example Street

命令

ln
硬链接 只删除一个链接不会导致文件的数据块和目录的连接被释放,inode与源文件相同
ln f1 f2 
软连接 类似于windows的快捷方式,inode与源文件不同,只存有链接文件的位置信息
ln -s f1 f3 
删除源文件,对硬链接无影响,会导致软连接无法访问,同时删除源文件,硬链接,文件才会被真正删除
cp
cp -r /home/stmrep/STMREP/collect/. /stmrep/incoming 文件夹下文件拷贝到另一个目录
kill
sudo kill -9 \`pidof app_data\`
dirname basename
dirname /usr/bin/sort # /usr/bin
dirname stdin.h # . 无/则获取当前目录
cd $(dirname "$0") # 跳转到脚本所在的路径
basename /usr/include/stdio.h .h # stdio 去掉文件名或后缀
basename /usr/include/stdio.h # stdio.h 
/dev/null 2 > &1
cmd >a 2>a: stdout和stderr都会送往文件a, 啊会被打开两遍,由此会相互覆盖
cmd >a 2>&1 stdout送往a,stderr继承了FD1的管道后再送往a,这样a只会被打开一遍,不会相互覆盖
./test.sh > test2.log 2>&1
# line1 : command not found  stderr
# date stdout
cmd > alloutput.txt 2 >&1
cmd &> output.txt # stdout和stderr都重定向到同一文件中
curl
# post请求是把参数放在HTTP消息主体发送,get请求是利用URL来发送参数
curl url -d "name=Clif&url=www.noucorp.com&http=..."
curl -H "Content-Type:application/json" -X POST -d '
{"username": "admin",
"password":"Admin@storage1",
"scope":"0",
"loginMode":"4"}' 
-k -s -c $cookiefile https://${storage_ip}:8088/deviceManager/rest/xxxxx/login

curl -s -H "iBaseToken:7AFECB16F6B38FC87CB8DA00C063D90A39FEDC324C0CAF6CAD8150879A1B33F5" -b "ismsession=20779D3499EBF06DB67CA271969AE2C693087CADB8AF38BC5C30CA4B64B524EA" -k -X GET https://8.46.8.110:8088/deviceManager/rest/2102351NYH10J9000013/diskpool/count
--user-agent 设置代理 # curl URL --user-agent "Mozilla/5.0"
-b 发送cookie -b 'foo=bar' -b 'foo2=baz' -b cookies.txt
--cookie curl http:\\www.example.com --cookie "user=username;pass=hack"
-c 将服务器设置的cookie写入文件 
-C 指定文件偏移处下载 -C offset(以字节为单位整数) -C -URL 断点续传
-d 发送post请求的数据体 -d 'login=emma&password=123' -d '@data.txt' 自动加上表头 Content-type:application/x-www-form-urlencoded
--data-urlencode 类似-d 区别会自动对发送数据进行url编码 --data-urlencode 'comment=hello world'
-F 上传二进制文件 -F 'file=@photo.png' 会加上请求头Content-Type:multipart/form-data 将png文件作为file字段上传  -F 'file=@photo.png;type=image/png' 指定MIME类型 -F 'file=@photo.png;filename=me.png' 指定服务器接收到的文件名是me.png
-H 添加HTTP请求头 -H 'Accept-Language:en-US' -H 'Secret-Message:xyz'
-I --head 只打印头部信息 不用下载整个文件
-d '{"login":"emma"}' -H 'Content-Type:application/json'
-k 跳过ssl检测 不会检查SSL证书是否正确
--limit-rate 限制请求和响应的带宽,模拟慢网速 --limit-rate 200k 带宽限制在200k
--max-filesize 可下载最大文件的大小 下载成功返回0 失败返回非0
-o 将响应保存为文件 -o example.html
-O 将响应保存为以URL最后部分命名的文件 -O http://.../bar.html 文件名:bar.html
-s --slient不再输出错误信息 不发生错误 就正常显示 -s -o /dev/null 不显示任何结果
-S 只输错误结果 
-u 设置服务器认证的用户名密码 -u 'bob:12345' 将其转换为HTTP标头 Authorization: Basic Ym9i0jEyMzQ1
-v --trace 输出通信整个过程
-x 指定HTTP请求的代理 不指定默认HTTP
-X 指定请求方法 -X POST
cat
cat -s 压缩相邻的空白行
cat -T 将制表符标记成^T
cat -n 加上行号
head tail
head file # 打印10行
head -n 4 # 打印前4行
head -n -5 # 打印除了最后5行以外的所有行
tail -n 5 # 打印最后5行
tail -n 1 # 打印最后一个
tail -n +6 # 除了前5行之外的所有行
tail -f file # 从尾部不断监视文件的内容 -s 睡眠间隔
PID=$(pidof gedit)
tail -f file.txt --pid $PID # 监控文件,当关闭编辑器 tail结束
find
find /home -name '*.txt' -print
# . 表示当前目录
find . \( -name '*.txt' -o -name '*.pdf' \) -print # -o 或 -and 与
find . -regex '.*\.\(py\|sh\)$' 匹配.py或.sh文件 -iregex 忽略大小写
find . ! "*.txt" -print 排除匹配到的文件
find . -type d -print 只列出目录
find . -type l -print # 列出当前目录下的符号链接
find . -type f -name "*.php" ! -perm 644 -print 找到权限不是644的php文件
find . -type f -name "*.swp" -delete 删除匹配的文件
find path -type f -name "*.mp3" -exec mv {} target_dir \ # 将mp3文件移入给定的目录
find path -type f -exec rename 's/ /_g' {} \ # 将找到文件的所有空格替换为_
find . -exec sh -c 'echo -n {} | tr -d "[:alnum:]_.\-" | tr "/" " "; basename {}'
# mail
#   statistics 将文件名的字母数字下划线等去掉,再把斜线转换为空格,再获取最后一级目录或文件显示目录树
for d in `find . -type d`
  do
  echo `find $d -type f | wc -l` files in $d # 103 files in . 统计目录下文件数
done                                         # 17 files in ./cpus ...
# 找到文件进行替换
find . -name *.cpp -print0 | xargs -I {} -0 sed -i 's/Copyright/Copyleft/g' {}
find . -name *.cpp -exec sed ...'' \{\} \
find . -name *.gcda | xargs -n 1 -i cp -rf --parents(保留文件属性) {} /home/BDM_HLT/build/build/dorado_aa_code/AA-BDM/ 
xargs
xargs命令重新格式化stdin接收到的参数,再将其作为参数传递给指定命令
cat example.txt | xargs # 将多行输入转换为单行输出
cat example.txt | xargs -n 3 # 设置每行3个 参数 
cat example.txt | xargs -n 1 ./cecho.sh # ./cecho.sh arg1 arg2 arg3 xargs添加的命令放在指定命令的尾部
cat example.txt | xargs -I {} ./cecho.sh -p {} -l # 命令替换站位
# ./cecho.sh -p arg1 -l
# ./cecho.sh -p arg2 -l
find source_code_dir_path -type f -name "*.c" -print0 | xargs -0 wc -l # 统计指定目录下c程序文件的行数
# 最好添加0字符作为结束标志符 再用xargs用0分隔 因为xargs默认用空格分隔
# -I -i 将一行赋值给后面的替代符
find . -name '*.sh' | xargs -I ^ sh -c "echo -ne '\n ^: '; grep main ^" # 找出所有sh脚本下 含main的行并打印 sh -c 是调用子shell来执行命令行脚本
 ./test_token.sh: 
 ./rest_test.sh: 
 ./test.sh: main 1121212122
find . -type f -exec du -k {} \; | sort -nrk 1 | head # 找出指定目录中最大的十个文件
tr
可以对标准输入的内容进行替换,删除,压缩
tr [options] set1 set2 集合1映射到集合2 当1大于2,2会不断复制1最后一个字符;当2大于1,2多余部分被忽略
echo "HELLO" | tr 'A-Z' 'a-z' # hello
集合定义 'ABD-}' 'aA.,' 'a-ce-x' 'a-c0-9' '\t' '\n'
echo 12345 | tr '0-9' '987654321' # 87654
tr '\t' '' < file.txt # 制表符替换
# 删除
echo 'Hello 123 World' | tr -d '0-9' # Hello World
# 补集
echo hello 123 | tr -d -c '0-9' # 删除不在0-9中的字符 123
echo hello 1 char 2 | tr -c '0-9' ''  # 将不在0-9的字符替换为空格 1    2
# 压缩
echo 'GUN is       not   Unix' | tr -s '  ' # 去掉多余空格 GUN is not Unix
cat sum.txt | echo $[ $(tr '\n' \+) 0 ] # 将换行符换为+,并在最后填0,将1+2+3+4+5 赋给$ [] 进行运算 15
# first 1
# second 2
# third 3
cat test_tr.sh | tr -d [a-z] | echo "$[ $(tr " " "+") ]" # 6 牛皮
# 字符类
tr [:class:] [:class:]
users | tr ' ' '\n' | sort
sort
sort,uniq 从特定文件或stdin读取输入,并写出stdout
sort file1.txt -o(>) sorted.txt
sort -n # 按数字排序
sort -r # 逆序
sort -M # 月份
sort -m sorted1 sorted2 # 合并两个排好序的文件
sort 1.txt 2.txt | uniq # 找出已排序文件中不重复的行
if sort -c fileToCheck ; then echo sorted ; else echo unsorted ; fi
sort -nrk 1 data.txt # 按照第一列 逆序排序 -nr 按照数字逆序排列 k 指定排序依照字符 单个字符表示列号
sort -bk 2.3,2.4 data.txt # 按照第二列的起始第三个字符终止第四个字符排序
sort -nk 1,1 data.txt # 以第一个字符排序
sort -z data.txt | xargs -0 # 是sort输出与以\0为终止符的xargs匹配
sort -bd #  忽略空白按照字典顺序排序
# 与uniq结合
sort unsorted.txt | uniq # 显示不重复的行 <=> sort -u unsorted.txt 去重
    ls -1 /stmrep/incoming/*_VMAX8_linuxa_* | awk -F"/" '{ print $NF }' |  cut -d"_" -f4 | sort -u | egrep -vi 000297801855
sort unsorted.txt | uniq -c # 统计各行在文件中出现的次数
uniq -d # 找出重复的行
uniq -s 2 -w 2 # 指定跳过前两个字符 使用接下来的两个字符
uniq -z file_names.txt | xargs -0 rm # -z生成0字节终止字符,将多个重复的文件名只传递给rm一次,防止出现rm重复文件名不存在的错误
... sort | uniq # 管道去重
dd
克隆给定的输入内容,将一模一样的副本输出
dd if=/dev/zero of=junk.data bs=1M count=1 # 创建内容全为0,1MB大小的文件junk.data
wc
wc -l file # 统计行数
cat file | wc -w # 统计单词数
wc -c # 统计字符数
wc -L # 最长那行的长度
    LINE_CNT=`wc -l $NEEDED_F | awk '{ print $1 }'`
    LINE_CNT=${LINE_CNT:-0} 文件长度
cut
提取指定位置或列之间的字符
cut -f 2,3 filename # 提取2,3列
--complete 显示没有被-f指定的字段
cut -f2 -d"," # 以分好分割,取第二列
N- 从第N个到行尾
N-M 从N个到M个
-M 从第一个到第M个
-b 字节
-c 字符
-f 定义字段
cut -c2-5 打印第2到第5个字符
cut f.txt -c1-3,6-9 --output-delimiter "," # 指定输出以逗号分割
# abc,fghi
# abc,fghi
echo "(123)" | cut -d "()" # 123 去除括号
# 查看制定字段是否重复
grep "Bound Pool Name" $VMAX8_SYMDEV_V_INPUT_FILE | sort -u | wc -l
差并集
comm A B
apple
    carrot
    cookies
            gold
silver
输出第3列 # 交集
输出第1列 # A有B没有
输出第2列 # B有A没有
comm A B -3 | tr -d '\t' # A B中互不相同的行 去掉制表符 删除第三列
comm A B -2 -3 # A的差集
sort B C | comm - A # BC排序后和A比较

grep

  • 文本搜索工具,能使用正则表达式搜索文本并把匹配的行打印出来
  • 命令格式:grep [option] pattern file

命令参数

-a 将二进制文档以文本方式处理
-A(n)除了显示匹配行,还要显示之后的n行 -A2
-b 显示该行第一个字符的编号 出现在行中的偏移
-B(n) 除了显示匹配行,还要显示之前的n行
-c 显示符合条件的匹配行数
    if [ `echo "${INPUT_FILE_PATTERN}" | grep -c "<env>"` -eq 1 ]
    then
        INPUT_FILE_PATTERN=`echo "${INPUT_FILE_PATTERN}" | sed "s/<env>/${LOCAL_SCRIPT_PARAM_ENV}/"`
    else
        INPUT_FILE_PATTERN="${INPUT_FILE_PATTERN}*${LOCAL_SCRIPT_PARAM_ENV}*"
    fi
    if [ `grep -c '^Symmetrix ID:' $tmpPF` -eq 1 ]
    egrep -c 'Device Symmetrix Name|Device Capacity' $NEEDED_F
    VALUE_CNT=${VALUE_CNT:-0}
-C(n) 除了显示匹配行,还要显示该行之前之后n行
-e 指定多个匹配模式
-f 读取文件中的模式 一行代表一个模式
-i 不区分大小写
--include --exclude 使用通配符指定包含或排除某些文件
grep "main()" . -r --include *.{cpp} # --exclude "README"
-l 匹配模式所在的文件 -L 不在的文件
grep "test" file* -lZ | xargs -0 rm # 与xargs连用用-z使用0值作为文件名的终结符
-q 静默 不会输出内容 0表示匹配成功 非0表示匹配失败
-v 打印出不匹配的所有行
-n 显示出匹配项和行号
-x 全字匹配
-o 只显示匹配到的内容
-r(R) 递归查找文件夹
-E 支持正则表达式 <=> egrep

正则

^ $ .(非换行符的字符) [] 
*(0个多个先前字符) '*grep' 匹配一个或多个空格后紧跟grep的行
[^] 匹配不在这些范围内的字符 [^A-FH-Z]rep 匹配不包含^A-FH-Z开头,紧跟rep的字符
\(..\) 分组 标记匹配字符 # \(love\)  被标记为1
\< 匹配单词开始 '/<grep' 匹配包含以grep开头的单词
\> 匹配单词的结束 'grep\>' 匹配以grep结尾的单词的行
x\{m\} # 重复x m次
x\{m,\} # 重复x 至少m次
x\{m,n\} # 重复x m<= <=n次
() 将括号内容视为一个整体 ma(tri)?x 匹配max或matrix
| 匹配两边的任意一项 Oct(1st | 2nd)
\w 字符数字 <==> [A-Za-z0-9]
\W 非单词数字字符
\b 边界 单词锁定 # '\bgrep\b' 只匹配grep
cntrl: 控制非打印字符
graph: 图形提付
xdigit: 十六进制字符
[[:digit:]] [0-9] 外层中括号表示其中一个字节
[[:lower:]] [a-z]
[[:upper:]] [A-Z]
[[:alpha:]] [a-zA-Z]
[[:alnum:]] [0-9a-zA-Z]
[[:space:]] 空白字符
[[:punct:]] 标点符号

案例

( +[a-zA-Z]+[?,.]? +) # 匹配任意单词 
grep test test* # 查找前缀是test的文件中包含test字符串的文件
grep -r update /etc/api 递归查找文件夹
grep -r f_local_check_env NMAX8/ 找文件夹下所有文件中的匹配项
grep "^[^48]" 匹配行首不以48开头的行
grep "K...D" 匹配KD中间是任意三个字符
    20200327-1113_VMAX8_linuxa_000297801855_symcfg_show_pool.DG1_F_2
    # ls -1  /stmrep/incoming/*_VMAX8_linuxa_* | grep "_symcfg_show_pool...*$"
grep '/bin/bash$' /etc/passwd | cut -d: -f1 | sort -n -r | head -1 显示结果中ID号最大的用户
netstat -tan | grep 'LISTEN[[:space:]]*$' 以LINSTEN结尾行
grep '\(\<[[:alpha:]]\+\>\).*\1r' wangTest 匹配一行中存在以某一单词加r的单词的行 He love his lover
echo -e "1 2 3 4\nhello\n5 6" | egrep -c "[0-9]" # 2 只统计匹配行数
改进 ... | egrep -o "[0-9]" | wc -l # 6
grep "test_function()" . -R -n <=> find . -type f | xargs grep "test_function()"
# 查询IP地址
ifconfig wlan0 | egrep -o "inetaddr:[^ ]*" | grep -o "[0-9.]*"
# 查找文件G_SHA256_CHECK_FILE_NAME中包含完整file单词这行,将这行开头的[0-9a-f]替换为L_MD5_RET
sed -ri "/\<$file\>/s/^[0-9a-f]+/$L_MD5_RET/" $TMP_TAR_DIR/$G_SHA256_CHECK_FILE_NAME

关联数组

  • 关联数组允许用户自定义数组索引
  • 定义:declare -A array
# 赋值
array[index]=pear
array=([index1]=tom [index2]=wang [index3]='bash')

awk

  • awk是一种能处理数据,产生格式化报表的语言。
  • 每一行是一条记录,记录与记录之间用换行符作为分隔符"\n",每一列是一个字段,字段与字段间用空格或tab制表符作为分隔符
  • 工作方式:将每一行数据视为记录(record),每条记录以字段分隔符分为多个字段,再输出各字段的值。
  • 语法 awk [options] [BEGIN]{program}[END] [file]
-F fs 指定一行中的字段分隔符 默认空格
-f file 指定读取程序的文件名
-v var=value 定义awk程序中的变量和默认值
# 还有种方法也可以传递参数值, 变量紧跟在代码块后
var1="variable1" var2="variable2"
echo | awk '{print v1,v2}' v1=$var1 v2=var2
注:'{}' 要将脚本命令放在大括号之内,大括号包在单引号之内
  • 优先级:1.BEGIN:开始处理数据流之前,可选 2.program:处理数据流,必选 3. END:处理完数据流之后执行,可选

基本用法

  • 字段相关内置变量 0 整行文本1文本中第一个字段 2 第二个数据字段N 第N个数据字段 $NF最后一个数据字段
awk '{print $0}' test 打印整个文本
awk '{print $6}' 打印每一行的第6个字段
awk '{print $NF}' 打印每一行最后一个字段
awk -F ':' '{print $1,$3,$NF}' /etc/passwd 对每一行以:分隔字段,并打印第1,3,和最后一个字段
awk -f abc test # abc为写入awk program的文件 {print $1,$3} 根据文件内容处理流
awk -v name='wang' 'BEGIN{preint name}' 定义变量 输出该变量 BEGIN可以不用流 数据源
# 双引号里使用命令参数 并使用getline接受回显
awk 'BEGIN{"echo hahaha" | getline; print $0}'
  • 提取某一行:提取记录 NR 指定行号
awk 'NR==3{print $0}' test 打印第三行 <==> awk 'NR==10' file.txt # 打印第十行
awk '$1=="3"{}print $0' 打印第一个字段是3的那一行的第一个字段
awk 'NR==M, NR==N' file # 打印从M行到N行的文本
  • 提取某个字段
awk 'NR==3{print $6}' 第三行第6列
  • 程序优先级:BEGIN最高优先级代码块,不需要提供数据源,不依赖PROGRAM;PROGRAM是对数据源进行处理,必选代码块;END在处理完数据源后进行操作,依赖PROGRAM,无法单独执行
awk 'BEGIN{print "hello world"}{print $0}END{print "bye world"}' test
awk 'BEGIN{print "hello world"}' begin不需要数据源
awk '{print "hello"}'
awk 'END{print "hello"}' 无数据源 无法执行

高级用法

cat collect_VMAX8.input | awk -F";" '$1 == "000297700138" { print }' | wc -l # 第一行发现了
getline
  • 如果找到一条记录,返回1,到了结束(EOF)返回0,错误返回-1
awk 'BEGIN{"cat data.txt" | getline d; print d}' data2.txt
awk 'BEGIN{"cat data.txt" | getline; print $0}'
awk 'BEGIN{getline d < "data.txt"; print d}'
# 打印data.txt的第一行
awk '{getline < "data.txt"; print $0}' # awk首先读取第一行,getline把下一行给$0
awk 10 | awk '{getline;print}' # 打印偶数行
awk 'BEGIN{"date" | getline date; print date}' # 打印日期
awk 'BEGIN{ while( getline line < "grep.test" ){ print line } }' # 读取文件
# 在begin语句块读取文件头部信息,主语句块读取余下信息
seq 5 | awk 'BEGIN{getline; print "first line", $0} 
{print $0}' # first line 0
# 2
# ...5
# 使用双引号可以读取命令输出
awk 'BEGIN{FS=":"}{"grep root /etc/passwd" | getline; print $1,$6}'
awk数组
  • 定义数组:数组名[index] = value
  • awk数组是一种关联数组 下标可以是数字或者字符串 支持多维数组,awk默认会吧数字下标转为字符串,本质上是是使用字符串作为下标的数组
  • awk数组可以定义空字符串 huluwa[5]="",因此不能用huluwa[5] == ""来判断元素是否存在 ,在引用不存在的元素时也会自动创建 因此 huluwa[6] == ""也不能用来判断元素时会否存在 应当使用 if (6 in huluwa) {print "6号元素存在"}
if !(6 in huluwa){print "第6个元素不存在"}
遍历数组, 无序
awk 'BEGIN{array[0]=100;array[1]=200;print array[0],array[1]}' # 100 200
{for (item in array) print item,array[item]}
  • 实例
{a="test";print a;a=a+1;print a;a++;print a} # test 1 2 当变量为字符串参与运算时,会被当做0进行运算
{print test["ip"];test["ip"]++;test["ip"]++;print test["ip"]} # 空 2 
# 当引用了不存在的元素时,元素被赋值为空字符串,在进行自加时,就往上加1
# 统计ip
192.168.1.1
192.168.1.2
...
awk '{count[$1]++}END{for (i in count){print i,count[i]}}' test10
192.168.1.12 1
192.168.1.1 2
...
# 统计人名出现次数
Allen Philip
Wang...
awk '{for (i=1;i<=NF;i++){count[$i]++}}END{for (i in count){print i,count[i]}}'
Jack 1
James 1 ...
  • 二维数组
awk '
BEGIN{
    for(i=0;i<3;++i){ 
        for(j=0;j<4;++j){
            dict[i,j]=100
        }
    }
}
END{
  for(key in dict) {
    split(key, subkey, SUBSEP); # 分隔字符串
    print subkey[1], subkey[2], dict[subkey[1],subkey[2]
  }
}' $*
正则
# 打印位于开始模式到结束模式之间的文本
awk '/start_pattern/, /end_pattern/' file
echo -e "cat\nbat\nfun\nfin" | awk '/f.n/' # fun fin fan
... | awk '^The' # There Their
... | awk '/[CT]all/' # Call Tall
'/Colou?r/' # u变成可选字符 Colour Color 
                /bin/gawk '
                     /Pool Name/ {
                         poolname=$4;
                       }
                     /Enabled Devices/ {
                         flag="enabled";
                       }
                     /Disabled Devices/ {
                         flag="disabled";
                       }
                     $NF == "Enabled" && flag == "enabled" {
                               # Sym dev Enabled Devices
                         DEVICE=$1;
                                 # Alloc MBs
                         DEVICE_USED=int($3)/1024;
                         gsub(" ", "", DEVICE);

                         if (pooltype == "Thin" && "'${SRP_NAME}'" != "")
                         {
                           print DEVICE";"DEVICE_USED";enabled;'${SRP_NAME}';;";
                         }
                         else
                         {
                           print DEVICE";"DEVICE_USED";enabled;"poolname";;";
                         }
                       }
                     $NF == "Disabled" && flag == "disabled" {
                         DEVICE=$1;
                         DEVICE_USED=int($3)/1024;
                         gsub(" ", "", DEVICE);

                         if (pooltype == "Thin" && "'${SRP_NAME}'" != "")
                         {
                           print DEVICE";"DEVICE_USED";disabled;'${SRP_NAME}';;";
                         }
                         else
                         {
                           print DEVICE";"DEVICE_USED";disabled;"poolname";;";
                         }
                       }' \
            >> $VMAX8_DEVICE_OUTPUT_FILE_TMP_CAPACITY
    
    cat $VMAX8_SYMCFG_SA_INPUT_FILE | \
                /bin/gawk -F":" '
                       {
                         if ( $1 ~ "Director Identification" ) { FA=$2; }
                         if ( $1 ~ "Director Type" ) { PORTTYPE=$2; }
                         if ( $1 ~ "Director Port$" ) { PORTNAME=FA":"int($2); }
                         {
                           PORTSTATUS=$2;
                           sub("line", "", PORTSTATUS);
                         }
                         if ( $0 ~ "Access_Logix" ) { MASKING_ENABLED=$2; }
                         if ( $1 ~ "Show_ACLX_Device" )
                         {
                           print PORTNAME";"PORTWWN";"PORTTYPE";"PORTSTATUS";"MASKING_ENABLED";"
                         }
                       }' | \
            sed "s/^/${LOCAL_SCRIPT_INPUT_TIME};${LOCAL_SCRIPT_COLLECTION_DATE_TIME};flat file;${SSUB_SID};${SSUB_MODEL};emc;/"  | \ sed -e "s|; |;|g" >> $VMAX8_PORT_OUTPUT_FILE
运算
赋值运算
awk -v name='wang' 'BEGIN{print name}'
awk 'BEGIN{school="wang";print school}'
比较运算 > >= == <= !=
awk 'BEGIN{print "a" >= "b"}' # 0 非真
awk '$1>4{print $0}' 打印第一个字段大于4的行到的第0个字段
数学运算 + - * / % ** ++ --
awk 'BEGIN{print 100/3}' # 33.333
awk -v 'count=0' 'BEGIN{count++;print count}' # 1
逻辑运算
awk 'BEGIN{print 100>=2 && 100>=3}' # 1
匹配运算 ~ ~!
awk -F ':' '$1 ~ "^ro"{print $0}' /etc/passwd 打印所有第一个字段以ro开头的行
awk -F ':' '$1 !~ "^ro"{print $0}' 打印所有第一个字段不包含ro的所有行
环境变量
# FILEWIDTHS:以空格分隔的数字列表,用空格定义每个数据字段的精确宽度
awk 'BEGIN{FILEWIDTHS="5 2 8"NR==1{print $1,$2$3}}' /etc/passwd # 源行root:x:0:0:root:/root:/bin/bash => root: x: 0:0:root 
# FS:指定数据源中的字段分隔符 类似-F
awk 'BEGIN{FS=":"}NR==1{print $1,$3,$NF}' /etc/passwd # root 0 /bin/bash
# OFS:指定输出到屏幕后字段的分隔符
awk 'BEGIN{FS=':';OFS="-"}NR==1{print $1,$3,$NF}' # root-0-/bin/bash
# RS:指定记录的分隔符 以什么方式分隔行
awk 'BEGIN{RS=""}{print $1,$13,$25,$37,$49}' test # 本来是有多行 以空行为分隔符分隔行时,所有字段就都在一行了 1 2 3 4 5
# ORS:输出到屏幕的分隔符 默认回车
awk 'BEGIN{RS="";ORS="*"}{print $1,$13,$25,$37,$49}' # 1 2 3 4 5*[root@wang] 提示符和输出符在一起了 因为默认回车符换成了*
流程控制
if 判断语句
awk '{if($1>5)print $0}' num 打印num中第一个元素大于5的行
awk '{if($1>5)print $1/2;else print $1*2}' num
for 循环语句
awk '{sum=0;for (i=1;i<4;i++){sum+=$i}print sum}' num2 将每行的前三个元素相加并打印
awk '{for(i=1; i<=NF; i++) {print $i}}'
controller_id_str=$(echo ${controller_id_array[@]} | awk '{for(i=1; i<=NF; i++) {print $i}}' | sort -u)
STM_SCRIPTS_RUN_CMD=`echo $STM_SCRIPTS_RUN_CMD | awk '{
    flag="no";
    str1="";
    for(i=1; i<=NF; i++){
        if (flag=="yes") {
            str1=str1"**** "
            flag="no"
            continue
        }
        if ($i=="-e"){
            flag="yes"
            str1=str1$i" "
        } else {
            str1=str1$i" "
        }
    }
    print str1
}'`
while 循环语句
awk '{sum=0;i=1;while(sum<150){sum+=$i;i++}print sum}' num2 当每行的和大于150时就终止这行的循环
do while 循环
awk '{sum=0;i=1;do{sum+=$i;i++}while(sum<150);print sum}' num2
# 包含
if ( $1 ~ "Director Identification" ) { FA=$2; }

ii=0
{
    # cat collect_VMAX8.input | grep -v "^#" | awk '{print $1}'
    if [ "${LOCAL_SCRIPT_PARAM_VMAX_SID}" = "all" ]
    then
        cat ${VMAX8_INPUT_FILE} | \
        grep -v "^#" | \
        awk '{ print $1 }'
    else
        cat ${VMAX8_INPUT_FILE} | \
        awk '{ print $1 }' | \
        awk -F";" '$1 == "'${LOCAL_SCRIPT_PARAM_VMAX_SID}'" { print }'
    fi
} | \
awk -F";" '{ print $1, $2 }' | \
while read VMAX_SID VMAX_NAME
do
    (( ii+=1 ))
    A_VMAX_NAME[$ii]="${VMAX_NAME}" # 将VMAX名字,SID存入列表中
    A_VMAX_SID[$ii]="${VMAX_SID}"
done
特殊变量
awk 'END{print NR}' test # 统计行数 5 NR在函数体中是当前的行数,在END中是最后一行的行数
awk 'END{print $0}' test 打印文本最后一行
awk 'END{print NF}' test 打印文本列数 看长度可用
    basename 20200327-1113_VMAX8_linuxa_000297801855_symcfg_v_list | awk -F"_" '{ print $(NF-3) }'
    # 000297801855
    # 看倒数几个的值
    `basename ${DORADO_FC_PORT_INPUT_FILE} | awk -F"_" '{ print $(NF-3) }'`
# 逆序
seq 9 | awk '{lifo[NR]=$0}END{
    for(lno=NR;lno>-1;lno--){ # END里的NR表示最后一行
      printlifo[lno]
    }
}'
处理函数
length(string) 返回字符串长度
index(string,search_string)
split(string, array, delimiter) # 将delimiter作为分隔符分割string,结果存入array
substr(string, start-position, length) # 返回子串
    匹配 DG1_F_F  TEFEEI RAID-5(7+1)    21742396    4589136   17153264   79    0
    awk 'substr($2,1,1) == "T" && substr($3,1,1) != "U" && ( NF == 8 || NF == 9 ) { print $1 }' ${OUT_PREF_19}_symcfg_list_pool
    echo "TEFEEI" | awk '{TYPE=substr($1, 2, 1) ;print TYPE} # E
    echo "TEFEEI" | awk '{TYPE=substr($1, 2, 2) ;print TYPE} # EF
sub(regex, replacement_str, string) # 将正则表达式regex匹配到的第一处内容替换为repalcement_str
    sub("^ *", "", $i) 去掉空格
    gsub(" ", "", SSUB_DEVICE_NAME) 
    awk '{gsub(/^\s+|\s+$/, "");print}' 去掉前后空格
    sub("^ *| *$", "", param_array[2])
gsub(...) # hesub类似,不过会替换所有内容
    awk '{gsub(/"|\[|\]|data:|,|{|}/,""); print $0}') 
match(regex, string) # 检查字符串能否找到匹配,找到返回0,否则返回非0
tolower(param_array[1]) # 大写变小写

实例

cat contacts
Riley Mullen
123 Main Street
Chicago,IL 60602
(312)555-1234

Riley Mullen
123 Main Street
Chicago,IL 60602
(312)555-1234

Riley Mullen
123 Main Street
Chicago,IL 60602
(312)555-1234
awk 'BEGIN{RS="";FS="\n"}{print $1,$4}' contacts # 先用空白分隔行 再以回车分隔列 取1,4列
Riley Mullen (312)555-1234
Riley Mullen (312)555-1234
Riley Mullen (312)555-1234
# cat ip
192.168.1
192.168.2
192.168.3
172.16.3
192.16.1
192.16.2
192.16.3
10.0.4
awk 'BEGIN{RS="",OFS="\n"}{print $1 "-" $3,$4,$7 "-" $4,$8}' ip
192.168.1-192.168.3
172.16.3
192.16.3-172.16.3
10.0.4
awk '{sum+=$1}END{print sum}' num 求所有第一个字段的和
# 统计连接状态的数量
netstat -antpl | awk '{array[$6]+=1}END{for(i in array) print array[i],i}'
4 LISTEN
3 SYN_RECV
3 ESTABLISHED
1 established)
1 FIN_WAIT1
1 Foreign
42 TIME_WAIT
统计词频
  • 写一个 bash 脚本以统计一个文本文件 words.txt 中每个单词出现的频率。你的脚本应当输出(以词频降序排列)
the 4
is 3
sunny 2
day 1
awk '{i=1;while($i!=""){array[$i]+=1;i++}}END{for (item in array) {print item,array[item] | "sort -r -n -k2"}}' words.txt
或 egrep -o "\b[[:alpha:]]+\b" $filename | awk '{count[$0]++}END{printf("%-14s%s\n,"word","Count");
for(ind in count){
  printf("%-14%d\n",ind,count[ind])
}
}'
有效电话号码
  • (xxx) xxx-xxxx 或 xxx-xxx-xxxx。(x 表示一个数字)
awk '/^([0-9]{3}-|\([0-9]{3}\) )[0-9]{3}-[0-9]{4}$/' file.txt 
转置文件
name age
alice 21
ryan 30
应当输出:
name alice ryan
age 21 30
awk '
{
    for(i=1;i<=NF;i++){
        array[NR,i]=$i
    }
}
END{
    for(i=1;i<=NF;i++){
        for(j=1;j<=NR;j++){
            if(j==NR) 
                printf array[j,i];
            else 
                printf array[j,i]" "
        }
        print ""
    }
}' file.txt
删除重复文件
ls -lS --time-style=long-iso | awk '
BEGIN{
  getline; getline;
  name1=$8; size=$5
}
{
  name2=$8;
  if (size==$5){
    "md5sum" "name1" | getline; csum1=$1;
    "md5sum" "name2" | getline; csum2=$1;
    if ( csum1 == csum2 ){
      print name1; print name2
    }
  }
  size=$5; name1=name2;
}
' | sort -u > duplicate_files # 得到所有重复文件名的文件
cat duplicate_files | xargs -I {} md5sum {} | sort | uniq -w 32 | awk '{print $2}' | sort -u > unique_files # 在一组重复文件中取出一个特有文件作为独有文件存入
echo "removing..."
comm duplicate_files unique_files -3 | tee /dev/stderr | xargs rm # 删除所有在重复文件不在独有文件的文件,相当于每一组重复文件只保留一份
echo removed duplicates files successfully.
枚举文件类型信息
path=$1
declare -A statarray
while read
do
  ftype=`file -b $line | cut -d, -f1` # 只显示文件类型
  let  statarray[$ftype]++;
done < <(find $path -type f -print) # 第一个< 用于重定向 第二个用于将子进程输出转换为相应的filename 文件名 加空格防止 解析为 <<

for ftype in "${!statarray[@]}";
do
  echo $ftype: ${statarray["$ftype"]}
done
# Vim swap file: 1
# ASCII text: 1
查看目录
ls -d */
ls -F | grep "/$" 
ls -l | grep "^d"
find . -type d -maxdepth 1 -print'''
压缩JS 丑化
cat example.txt | tr -d '\n\t' | tr -s ' ' |  # 去掉空行 制表符 多余的空格
sed 's:/\*.*\*/::g' | # 去掉JS注释 /* content */
sed 's/ \?\([{}();,:]) \?\/1/g' # 去掉 {,} (,) ; 前后的空格
ping主机
for ip in 192.168.{1..255}
do
(
  ping $ip -c 2 &> /dev/null
  if [ $? -eq 0 ]
  then
    echo $ip is alive
  fi
)& # ()中的命令会在子shell中执行 &放在后台
done
wait # 等待所有子进程结束
fping -a(显示所有活动主机ip) -u(显示不可达主机ip) -g(生成一组ip)

sed

  • linux提供的一个外部命令,stream editor 行(流)式编辑器,非交互式的对文件进行增删改查
  • 原理:一次处理文本中的一行 -> 存到缓存中 -> 处理完毕输出到屏幕
  • 语法:sed [options] '{command}[flags]' [filename]
命令选项
-e script 将脚本指定的命令添加到处理输入时执行的命令中 多条件,一行中多个操作
    echo abc | sed 's/a/A' | sed 's/c/C'
    sed 's/a/A;s/c/C'
    sed -e 's/a/A' -e 's/c/C'
    echo 123 | sed -e "s|^|456|" # 456123 头部插入
-f script 将文件中指定的命令添加到处理输入时执行的命令中
-n 抑制自动输出
-i 编辑文件内容
-i.bak 修改文件时同时将原文件保存到.bak备份文件
-r 使用正则表达式
! 取反
内部命令
a 在匹配后面添加
i 在匹配前面添加
p 打印
d 删除
s 查找替换
c 更改
y 转换 N D P
flags
g 新文本替换现有文本的全部实例
p 打印原始内容
w filename 将替换的内容写入文件
双引号引用
text=hello
echo hello world | sed 's/$text/HELLO'

内部命令

  • 追加操作
# 每一行后追加内容 append data "haha"
sed 'a\append data "haha"' data1
# 第二行后追加数据
sed '2a\append data "haha"' data1
# 第二到四行每行后新开一行追加数据
sed '2,4a\append data "haha"' data1
# 匹配字符串追加:找到3 the的行,在其后追加内容
sed '/3 the/a\append data "haha"' data1
  • 插入操作
# 每一行后插入
sed 'i\insert data' data1
sed '2i\insert data' 第二行后插入
sed '/3 the/i\insert data'
  • 替换操作 将一行中匹配到的内容替换为新的数据
# test替换为text
echo "this is a test" | sed 's/test/text/'
sed 's/dog/cat/' data1 每行的dog替换为cat
sed '2s/dog/cat/' data1
sed '2,4s/dog/cat/' data1
sed '/3 the/s/dog/cat' data1 将包含"3 the"的行中的dog替换为cat
  • 替换 所有
sed 'c\change data' data1 将文件所有行替换为change data
sed '2c\change data'
sed '2,4c\change data' 替换2,3,4行
sed '/3 the/c\change data' 
  • 修改 字符转换
sed 'y/abc/ABC' data1 将data1中的a b c转换为对应的A B C字符
  • 删除
sed 'd' data1 删除所有
sed '3d' data1 删除第三行
sed '3,4d' data1 删除3到4行
sed '/3 the/d' data1 删除所有满足匹配的行
sed '/^$/d' file # 删除文件中的空行
  • 打印
sed 'p' data1 打印文件内容 每行会重复打印一遍 因为打印了一次 有奖读入缓存的数据打印了一次 就看到打印了两次
sed '3p' data1 第三行打印了两次 可以使用-n抑制内存输出
sed '3,4p'
sed '/3 the/p'

命令选项

-e 命令行中使用多个命令
sed -e 's/brown/green/;s/dog/cat/' data1
-f 从文件中读取编辑器命令
sed -f abc data1
-n 抑制内存输出
sed -n '2,$p' data1 打印文件的第二行到最后一行 不再输出两遍
-r 正则
sed -n -r '/^(3 the)/p' data1 打印文件中以3 the开头的行
-i 修改源文件
-i.bak 备份原文件后再修改源文件

语句

sed -n 's/.*<title>\(.*\)<\/title>.*<author><name>\([^<*]\)<\/name><email>\([^<*]\).*/From: \2 [\3]'
\nSubject: \1\n/p
sed -e 's/ *Rank-\([0-9]*\) *\([0-9]*\) *\(.*\)/\1\t\2'
sed "s,^/,$baseurl/," > tmp/$$.list
sed 's$</*[a-z]*>$$g' # 删除所有标签

标志

# 替换一行中的第二处为cat
sed 's/dog/cat/2' data
# 将一行中所有满足条件的都替换
sed 's/dog/cat/g' data
sed 's/this/THIS/2g' # 替换第二次出现的位置
# 打印文本内容 类似-p
sed '3s/dog/cat/p'
# w filename 将修改的内容存filename
sed '3s/dog/cat/w text' data 将修改的第三行的内容存入text
# &表示模式匹配到的字符串
echo this is example | sed 's/\w\+/[&]/g' # [this] [is] [example]
# \#代表括号内容匹配到的子串 \(pattern)\ 匹配子串
echo this is digit 7 | sed 's/digit \([0-9]\)/\1/' # this is 7
echo seven SEVEN | sed 's/\([a-z]\+\) \([A-Z]\+\)/\2 \1' # SEVEN seven

小技巧

$= 统计文本有多少行
sed -n '$=' data # 5
打印内容时加上行号
sed '=' data

案例

# DNS监控服务状态
sed -i '192.168.18.240/s/^/;/' /var/named/baidu.zone 将包含192.168.18.240的行的首位个空位加上;
sed -i '192.168.18.240/s/;//' 去掉;
sed -i 's/\b[0-9]\{3\}/NUMBER/g' data.txt # 将所有三位数字替换为NUMBER
删除文件中包含特定单词的句子
# 一个句子的组成:空格+文本+匹配字符+文本+句号
sed 's/ [^.]*mobile phone[^.]*\.//g' # [^.]* 表示不是.的任意字符,匹配多次
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容