shell

Contents

一、这里都是一个个独立的小例子

1、遇到一个诡异的问题

目标:将文件名后缀去掉,然后在上层目录下,建立一个同名目录,然后将.zip压缩包的内容全部解压过去

# echo 文件名.zip | sed -e 's/\.zip$//' 这是将后缀名去掉
# echo 文件名.zip | sed -e 's/\.zip$//' | xargs -i mkdir ../unzipPath/{}

此时试图在上层目录创建一个文件夹,但是{}的内容为空,将其打印出来显示也为空
# echo 文件名.zip | sed -e 's/\.zip$//' | xargs -i echo ../unzipPath/{}

==>>试图变通,未果:
# echo 文件名.zip | sed -e 's/\.zip$//' | xargs -i mkdir -p {} && mv {} ../unzipPath/

继续尝试,将  xargs -i mkdir ../unzipPath/{} 改为  xargs -i mkdir ‘../unzipPath/{}’,则在目标文件夹发现,新增了一个目录,名称为随机乱码

但是 xargs -i mkdir ‘../unzipPath/{}’ 显示的却是预期的文件名

再次尝试: xargs -i mkdir ../unzipPath/\{\} 同样增加了一个乱码目录名,而 xargs -i echo ../unzipPath/\{\} 却是预期的文件名

第二个困惑,大目录查询报错问题,不知道如何解决

对查询全部项目中的文件,搜索关键字
grep -rni keyWords `find ../../ -name *.c`

这种格式很漂亮,能找出对应C文件,在哪行有关键字。
但是当目录下C文件太多时,会报错:
-sh: /usr/bin/grep: Arguments list too long

此时只能换一种写法:
find ../../ -name *.c | xargs -i grep -rni keyWords {} -color
此方法虽然也能查找到几个调用,但是却无法显示相应文件。
无法让人满意

二、linux shell 显示风格

PS1="\[\e37;40\] [\[\e[32;40m\] \u \[\e[37;40m\] @\h\[\e[36;40m\]\W\[\e[0m\]]\\$"
最后的\\$ 可以替换为 #,如果是\\#则会发生转意,打印操作行数

I、字符串替换小工具

[zhoujinhua@ prj] vi replace.sh

#显然 $# 表示参数的个数
if [ "$#" -lt "2" ];then
    echo 'error, please input src_str dst_str'
    exit
fi

# $1 表示第一个参数e
echo $1
echo $2

#先把包含关键字 $1 的文件打印出来
echo `grep -rn $1 ./ -rl`

# 所所有包含关键字 $1 的文件,里面的 $1 都替换成 $2
sed -i -e 's/$1/$2/g' `grep -rn $1 ./ -rl`

此脚本调用方法:
[zhoujinhua@ prj] ./replace.sh str_source str_dest

II、 shell 下程序级联调用

#!/bin/sh
python a.py

# 如果 a.py 返回值大于等于128,则执行 b.py
if [ $? -ge 128 ]; then
    python b.py
fi

二、patsubst 函数的级联使用方法

obj = $(patsubst %.c,%.o, $(patsubst %.S, %.o, $(patsubst %.s, %.o,$(DIR))))

解释:
1、$(DIR) 为一个目录,是一个路径变量,其展开后,可以是 *.c *.h *.s *.S ...
2、先执行第三个 patsubst:
      $(patsubst %.s, %.o,$(DIR)) 将其中所有 *.s 变成 *.o
3、再执行第二个 patsubst:
      $(patsubst %.S, %.o, $(patsubst %.s, %.o,$(DIR))) 将所有 *.S 变成 *.o
4、最后执行第一个patsubst,将所有 *.c 变成 *.o

三、echo -e的作用,man帮助的解释是,允许后面的输出进行转义

假设你是 echo -e "i will use \n $HOME" 输出的将是
i will use
/root(当前用户的主目录)

如果是 echo "i will use \n $HOME" 则输出是:
i will use \n $HOME

四、tar命令的使用

1、正常打包

tar -cvf ./abc.tar ./

2、排除某个目录打包

tar -vcf ./abc.tar ./  --exclude=./dir1 --exclude=./dir2/release
注意最后一个字符:
   是 --exclude=./dir1 而不是 --exclude=./dir1/

五、shell调试手段

1、相关资源

https://blog.csdn.net/u013516966/article/details/50913770

2、行号$LINENO,类似C语言__LINE__

3、函数名FUNCNAME,类似C语言__func__

    它是一个数组,${FUNCNAME[0]}为正在执行的函数名。而[1]是[0]的使用者,其余可以类推

4、为sh -x a.sh调试加上行号和函数名

 $PS4 第四级提示符变量,实际调试 sh -x 时,每行前打印的就是 $PS4,在 Bash Shell 中,缺省的 $PS4 值为 '+'号。
 利用此特性,通过一些内置变量重定义 $PS4 的值,即可达到增加调试的目的。

 如: export PS4='+{$LINENO:${FUNCNAME[0]}}'
   然后在调试时添加 -x 选项执行脚本,就能得到第一句执行的行号及函数名

5、调试技巧set -v

在每次执行语句时,希望看到执行的具体内容,当然采用 sh -x a.sh 的办法也是可以的
这里介绍 set -v; xxx ; set +v
如:
    set -v
    echo good
    set +v

六、shell 脚本参数

$# 表示参数个数
$1 表示第一个参数,$2 表示第二个参数 ...
$@ 表示参数列表,可以不做改动,继续向下面的函数传递

七、 shell脚本 shift命令的作用

1、shift 作用解释

如 shell 脚本的传入参数为 a b c d e
在脚本中执行一句 shift 2
则将会把入参变成: c d e

2、脚本样例

s

八、强制执行 shell 脚本生效

shell 脚本下,a.sh 中有一句:
cd ../../xx
切换到其它目录的语句。但是执行结束后,仍然在本地,没有实际切换过去
因为在 shell 脚本 a.sh 中,这一句执行完成之后,环境变量就释放了
如果需要使退出后仍然生效,可以这样写:
source cd ../../xx
然后再干别的操作。

或者另一种写法: cd ../../xx ; b.sh  也能达到效果,因为在同一行,环境变量还没有释放

九、获取 python 脚本的返回值,用于逻辑判断

1、例1

A、python 脚本的内容,返回一个值128

pi@raspberrypi:~/study/python $ cat a.py
exit(128)
pi@raspberrypi:~/study/python $

B、shell 脚本,判断其是否大于等于128

#!/bin/sh
python ./a.py
tmp=$?
echo ==$tmp==
if [$tmp -ge 128]; then  # 这里是错误语法
    echo "great or equal to 128"
else
    echo "little than 128"
fi
pi@raspberrypi:~/study/python $

C、注意事项,以上代码为错误代码,需要更正

pi@raspberrypi:~/study/python $ cat b.sh
#!/bin/dash
python ./a.py
tmp=$?
echo ==python return value : $tmp==
if [ "$tmp" -ge "128" ]; then
    echo "great or equal to 128"
else
    echo "little than 128"
fi
pi@raspberrypi:~/study/python $

i、注意1

shell 赋值语句,变量和值之间不能有空格,用等号直接相连
tmp = $?  ==>> 这是错误的写法
tmp=$?    ==>> 正确写法

ii、判断语句,中括号与变量之间,必须有空格,否则报错

if [$tmp -ge 128]; then    ==>> 写法错误,因为 $tmp 与 [ 之间没有空格
if [ $tmp -ge 128 ]; then  ==>> 正确写法

iii、大小符号意义

-eq  等于,如:if [ "$a" -eq "$b" ]
-ne  不等于,如:if [ "$a" -ne "$b" ]
-gt  大于,如:if [ "$a" -gt "$b" ]
-ge  大于等于,如:if [ "$a" -ge "$b" ]
-lt  小于,如:if [ "$a" -lt "$b" ]
-le  小于等于,如:if [ "$a" -le "$b" ]
<    小于(需要双括号),如:(("$a" < "$b"))
<=   小于等于(需要双括号),如:(("$a" <= "$b"))
>    大于(需要双括号),如:(("$a" > "$b"))
>=   大于等于(需要双括号),如:(("$a" >= "$b"))

iv、注意,raspBeryyPi推荐系统,运行的脚本是 /bin/dash

#!/bin/dash 而不是 #!/bin/sh
但是后来为什么两者效果没有差别?

D、最终执行效果

pi@raspberrypi:~/study/python $ cat a.py
exit(138)
pi@raspberrypi:~/study/python $ sh b.sh
==python return value : 138==
great or equal to 128
pi@raspberrypi:~/study/python $

pi@raspberrypi:~/study/python $ sh b.sh
==python return value : 118==
little than 128
pi@raspberrypi:~/study/python $

十、采用歪点子隐藏 shell 脚本输出

1、故意grep 查找一个不存在的字符串

cd -                  这条语句会打印新路径
cd - | grep error     这样就不会有输出

十一、shell 循环

1、for循环,算术类循环

A、风格一

#!/bin/bash
for((i=1;i<=10;i++));
do
    echo $(expr $i \* 3 + 1);
done

B、风格二

#!/bin/bash

for i in $(seq 1 10)
do
    echo $(expr $i \* 3 + 1);
done

C、风格三

#!/bin/bash

for i in {1..10}
do
    echo $(expr $i \* 3 + 1);
done

D、风格四

awk 'BEGIN{for(i=1; i<=10; i++) print i}'

2、字符性质循环

A、将当前目录的所有文件采用ls列出,然后逐一打印出来

#!/bin/bash

for i in `ls`;
do
    echo $i is file name\! ;
done

运行效果:
15900@DESKTOP-CGPRCF3 /cygdrive/d/bbbbbbbbb
$ sh test.sh
get_pic.bat is file name!
IMG_20181205_201517.jpg is file name!
IMG_20181207_195704.jpg is file name!
IMG_20181207_195721.jpg is file name!
IMG_20181207_195736.jpg is file name!
IMG_20181207_195749.jpg is file name!
IMG_20181207_195751.jpg is file name!
IMG_20181207_195755.jpg is file name!
IMG_20181208_123555.jpg is file name!
IMG_20181208_123826.jpg is file name!
IMG_20181208_184448.jpg is file name!
IMG_20181208_184458.jpg is file name!
IMG_20181208_184506.jpg is file name!
test.sh is file name!

B、将 shell 脚本参数逐个打印出来

$ cat test.sh
#!/bin/bash

echo para num is $#;
for i in $* ;
do
    echo $i is input chart\! ;
done

执行效果:
$ sh test.sh para1 para2 para3
para num is 3
para1 is input chart!
para2 is input chart!
para3 is input chart!

C、数组、列表类循环

#!/bin/bash

for i in f1 f2 f3 ;
do
    echo $i is appoint ;
done

运行效果:
15900@DESKTOP-CGPRCF3 /cygdrive/d/bbbbbbbbb
$ sh test.sh
f1 is appoint
f2 is appoint
f3 is appoint

D、字符串中的变量循环

15900@DESKTOP-CGPRCF3 /cygdrive/d/bbbbbbbbb
$ cat test.sh
#!/bin/bash

list="rootfs usr data data2"
for i in $list;
do
    echo $i is appoint ;
done

15900@DESKTOP-CGPRCF3 /cygdrive/d/bbbbbbbbb
$ sh test.sh
rootfs is appoint
usr is appoint
data is appoint
data2 is appoint

3、路径查找类循环

A、查找用户wishcell下所有文件,不进行遍历

#!/bin/bash

for file in /home/wishcell/*;
do
    echo $file is file path \! ;
done

[root@localhost server]# sh test.sh
/home/wishcell/app-debug.apk is file path !
/home/wishcell/jquery-3.1.1.js is file path !
/home/wishcell/jquery-3.1.1.min.js is file path !
/home/wishcell/ngrok.exe is file path !
/home/wishcell/poc_new.py is file path !
/home/wishcell/poc.py is file path !
[root@localhost server]#

B、查找当前目录下的所有.sh文件

[root@localhost server]# cat test.sh
#!/bin/bash

for file in $(ls *.sh)
do
    echo $file is file path \! ;
done
[root@localhost server]#

[root@localhost server]# ls
a.c  a.c~  a.sh  b.sh  server.c  server.c~  test.sh

[root@localhost server]# sh test.sh
a.sh is file path !
b.sh is file path !
test.sh is file path !
[root@localhost server]#

4、for循环嵌套

for file in `cat file_list`
do
    for line in `cat $file`
    do
        echo $line;
    done
done

十二、坑

1、变量初始化,赋值

a=3    成功
a = 3  失败

2、变量在双引号中能正常替换,在单引号中变成了常量

a=3
echo "$a" 正常显示3
echo '$a' 显示$a,是一个字符串,十分坑人

3、超级巨坑

本以为只有单引号才是坑。
没想到双绰号的坑更大,坑了好长时间才回过味来,多少个夜晚AO过了。

linux命令行输入如下内容,反应完全不同:
[wishcell@localhost config]$ ls
db.cfg  grpc.cfg

加了双引号这是普通字符串
[wishcell@localhost config]$ echo "a *"
a *

不加双引号,就变成了 ls 的作用。做梦也没想到。尤其是在脚本程序中:
[wishcell@localhost config]$ echo a *
a db.cfg grpc.cfg

十三、实际项目

1、项目名称:自动匹配IP,然后登录到相应的linux服务器

A、基础知识:字符串比较玩法

i、判断字符串的包含关系

15900@DESKTOP-CGPRCF3 /cygdrive/d/sync_picture
$ if [ "abcd"=~"abc" ];then  echo "good"; fi
good

ii、判断字符串是否为空(1)

15900@DESKTOP-CGPRCF3 /cygdrive/d/sync_picture
$ mystr=

15900@DESKTOP-CGPRCF3 /cygdrive/d/sync_picture
$ if [ $mystr -eq ]
> then
> echo "mystring is empty";
> fi
mystring is empty

15900@DESKTOP-CGPRCF3 /cygdrive/d/sync_picture
$ if [ $mystr -eq ]; then echo "mystring is empty"; fi
mystring is empty

iii、判断字符串是否为空(2)

15900@DESKTOP-CGPRCF3 /cygdrive/d/sync_picture
$ if [ -n $mystr ]; then echo "mystring is empty"; fi
mystring is empty

iv、判断字符串为空(3)

15900@DESKTOP-CGPRCF3 /cygdrive/d/sync_picture
$ if [ "$mystr"=="" ]; then echo "mystring is empty"; fi
mystring is empty

15900@DESKTOP-CGPRCF3 /cygdrive/d/sync_picture
$ if [ x"$mystr"==x ]; then echo "mystring is empty"; fi
mystring is empty

B、基础知识:函数的定义与用法

i、函数定义

function Match_and_connect(){
    echo "file name   : $1"
    echo "line content: $2"
    echo "file name key    : $3"
    echo "line content key : $4"
    return
}

ii、函数的调用

# $(Match_and_connect "file_name" "line_content" "file_name_key" "line_content");

不需要考虑返回值的调用方法:
Match_and_connect "file_name" "line_content" "file_name_key" "line_content"

2、具体的项目代码

A、项目脚本用法

$ sh auto_login.sh j_masterstart.sh

B、auto_login.sh 源码

# 首先执行传进来的确参数(其实是一个脚本)
sh $1 ; echo $?
sleep 1s
script_name=`echo $1 | tr a-z A-Z`
old_IFS=$IFS
IFS=$'\n'
the_ip=

function Match_and_connect()
{
    echo "script name : $1"
    echo "line content: $2"
    echo "script name key : $3"
    echo "line content key: $4"

    if [[ "$1" =~ "$3" ]];then
        if [[ "$2" =~ "$4" ]];then
            the_ip=`echo $2|tr -d "$4 IP:"`
            ssh $the_ip -l root
            break
        fi
    fi
}

# grep -i j_ RADME.txt -A 5 : 从 README中搜索关键字 "j_",不区分大小写,然后找到它,连同其后5行,共六行输出
# tr a-z A-Z 将输出中小写字符转换为大写
for line in `grep -i j_ RADME.txt -A 5 | tr a-z A-Z`
do
    echo "----- $line -----"
    $(Match_and_connect "$script_name" "$line" "MASTER"  "MASTER" );
    $(Match_and_connect "$script_name" "$line" "SLAVE"   "SLAVE"  );
    $(Match_and_connect "$script_name" "$line" "LINEONE" "LINEONE");
    $(Match_and_connect "$script_name" "$line" "LINETWO" "LINETWO");

    if [ -n "$the_ip" ];then
        break
    fi
done

IFS=$old_IFS

C、README.txt中内容的样子

......
I_server
master ip:192.168.0.60
slave ip:192.168.0.61
lineone ip:192.168.0.78
linetwo ip:192.168.0.70
USER

J_server
master ip:192.168.0.100
slave ip:192.168.0.101
lineone ip:192.168.0.88
linetwo ip:192.168.0.80
USER

K_server
master ip:192.168.0.110
slave ip:192.168.0.111
lineone ip:192.168.0.98
linetwo ip:192.168.0.90
USER
......

D、脚本的样子

......
i-masterstart.sh i-slavestart.sh i-lineonestart.sh i-linetwostart.sh
......

4、运行效果

十四、向批量文件中插入内容

1、需求描述

 有134个`.c`文件,需要向各个文件中插入一个`aa.h`,将其插入在第一行即可。

2、解决办法及步骤

 A、将所有文件集中到一个文件中
find ./ name "*.c" > list

B、对`list`中的文件逐一操作,`134`个文件瞬间完成
cat list | xargs sed -i '1i\#include \"aa.h\"'

十五、快速解决编译错误,进入编译状态

1、需求描述

编译过程中,由于依赖某个头文件。造成整个目录中百余个文件都需要添加一行头文件引用。
但是每次改完之后,都需要重新编译,才会知道下一个文件是什么名字。大量的编译和查找时间浪费,
人肉操作极其耗时、枯燥、乏味

2、自制工具,编译失败时自别哪个文件缺少头文件。主动打开,等待手工操作,这是半自动化。手工添加后保存退出

> touch qmake.sh
> chmode +x qmake.sh
>
> qmake.sh 中的内容如下
> clear; make > log 2>&1
>
> 注释:先把保存的 log 打印出来,然后过滤掉包含有 BOOL 行,因为具体项目中有干扰。最后再把包含有 error 关键字的行及其前一行打印打印出来。正好作为字符串变量,赋值给 str
> str=`cat log | grep -v BOOL | grep error -B 1`
>
> 这里是一个调试打印
> echo "str is :$str"
>
> filter_name_and_line(){
>     这是调试打印,正式代码不能存在,否则就是错误代码
>     # echo "para num:$# / all paras:$@ / para1:$1 / para2:$2..."
>     ret_str=`echo $2 | tr -s "[:]" "[ ]"`; echo $ret_str
> }
>
> 将字符串全局变量输入后,得到一个 文件名,空格,行数,这是一个调试打印
> echo $(filter_name_and_line $str)
>
> vim 已经可以粗略的对这个字符串进行编辑了,但是实上它带了两个参数,vim误把它当成两个文件。所以这个需要改进
> vim  $(filter_name_and_line $str)
>
> 把文件名、空格、等号的字符串,仅仅提取出文件名。只需要一个函数,把这个字符串当成参数传入,然后在函数体内,会把它当成多个参数,仅仅打印第一个参数就能够达到目标
> filter_only_file(){
>     echo $1
> }
>
> 注释:把之前的结果,filter_name_only 的参数,即达到目标。这是一个调试打印
> echo $(filter_only_file $(filter_name_and_line $str))
>
> 最终成品的样子
> vim  $(filter_only_file $(filter_name_and_line $str))

3、实际使用

每次输入 sh ./qmake.sh 后就会进入到一个 `.c`的打开状态,手动在里面添加一个头引用后。保存退出。
再次执行 ./qmake.sh,周而复始...

十六、快速用vimdiff对比目录,其中有大量同名文件

1、需求描述

有两个目录,存在大量同名文件,需要逐一对比,但是手工执行 `vimdiff /path1/file1 /path2/file1`效率很低,
不符合一个程序员的工作风格

2、思路

 一个目录,经过不同人员修改,现在需要合并而已
 首先获得文件列表。`ls /path/`得到全都是不带路径的文件名
 ls /path/
 file1 file2 file3 file4 file5 ...
然后每次对比一对文件。根据输入的索引不同,对比的文件名不同

3、根据BDD思想,需要先确定业务的使用方式,最后开发

用法:
> ```perl
> $ ./qvimdiff.sh
>  报错,提示需要带一个索引参数。表示第几个文件
>
> $ ./qvimdiff.sh 1
> 然后进入 vimdiff ,对比两个文件 /path1/file1 和 /path2/file1
>
> $ ./qvimdiff.sh 2
> 然后进入 vimdiff ,对比两个文件 /path1/file2 和 /path2/file2

4、代码实现

 if [ $# -lt 1];
 then
     echo "please input at least 2 parameter"
     echo "usage: ./qvimdiff.sh 1"
     exit
 fi

 此函数有两个参数,第一参数为索引,第二个是文件列表
 show_nth_para(){
     因为函数有两个参数,但是第二个参数是以空格相隔的字符中。所以对于 shell 函数来说,实际上是有N+1个参数的,N是文件列表的个数。所以真正的file1,它的索引为2,以此类推
     file_idx=$(($1+1))
     I=0
     while 后面的中括号,必须和参数之间保留一个空格,否则报错
     while [ $I -lt $file_idx ]
     do
         把后面的参数转移到最前而,shift 执行一次,转一下
         shift
         变量I自加操作
         I=$(($I+1))
     done
     这就是转了N次之后,所得到的第N个文件名,把它打印出来,供函数调用都提取返回值
     echo "/path1/$1 /path2/$1"
 }

 print_dir_nth_file(){
     ls 是一个函数,/path1/ 是一个参数,返回值赋值给 ls_list
     ls_list="$(ls /path1/)"

     这里的$1是函数调用者传递过来的,表示文件名索引,第几个文件
     show_nth_para $1 $ls_list
 }

 这里的 $1 是linux 命令行传递进来的,表示文件名索引
 vimdiff $(print_dir_nth_file $1)

5、对qvimdiff.sh进行升级

A、需求描述

由于对比的目录太多。一个大目录./root下有许多小文件夹。以上脚本不够强大,无法支持。

> ####现在希望,在`./root `下执行脚本,能把`root/dir1/source/a.c`,`root/dir1/include/a.h`以及`./root/dir2/source/b.c`,`./root/dir2/include/b.h` 都列举出来,并采用 `vimdiff`打开。
>
> ####如果`./root/dir2/source/a.c`不存在,则将`./root/dir1/source/a.c`删除

B、按照BDD设计思想,先把产品用法列出,列出实例化用例

> case 1: 仅仅在命令行输入 ./qvimdiff.sh ,后面不带文件索引参数,脚本应该报错并退出
>
> case 2,两个文件都存在,则用 vimdiff 打开两个文件,并处于编辑状态
> $ ./qvimdiff 1
> $ ./qvimdiff 2
> $ ./qvimdiff 3
> $ ./qvimdiff 4
>
> case 3: ./root/dir2/source/a.c 不存在,则要求删除 ./root/dir1/source/a.c,给出提示并退出

C、代码实现

第一部分,参数检查
if [ $# -lt 1 ]; then
    echo "error , please input file_idx"
    echo "usage: ./qvimdiff.sh 1"
    exit
fi

show_nth_para(){
    str_idx=$(($1+1))
    临时变量只能采用大写字母,不能小写
    I=0
    while [ $I -lt $str_idx ]
    do
        shift
        I=$(($I+1))
    done
    file1=$1
    这是一个 shell 正则表达式,将变量 file1 中的字符串 vomci 替换为 vomci2
    file2=${file1/vomci/vomci2}

    判断,如何第二个文件不存在
    if [ ! -f $file2 ]; then
        rm $file1
        echo "$file1 is removed"
        exit
    fi
    echo "$file1 file2"
}

print_nth_file(){
    find_str=`find $PWD/dir -name "*.[c|h]"`
    show_nth_para $1 $find_str
}

调试打印,看看输出是否符合预期
# print_nth_file $1

正式代码,这个 $1 是命令行传进来的第一个参数
vimdiff $(print_nth_file $1)

D、对qvimdiff.sh再次升级,只列列出不同文件,跳过相同文件

用到的知识点:
shell 下如何判断文件是否相同
首先看两个文件是否都存在`if  [ ! -f file_1 ]; then`
 然后用`md5sum`命令对两个文件计算,输出一个32位的字符串,后跟文件名
   sum1=`md5sum -t $file1`
   sum2=`md5sum -t $file2`

`md5sum`工具需要`root`权限才能使用
`md5sum`工具得到的字符串,前 32 位可以用来做对比,决断文件是否相
将前面`md5sum`
得到的字符串进行截取前`32`位。`id=${sum1:0:32}`

遍历函数所所有入参:
myFunc() {
   for i in $@
   do
      file1=$i
      shift
    done
}
myFunc aa bb cc dd

也可以写成
myFunc(){
    for i in $@ ;
    do {
        file1=$i
        shift
        echo $file1 is file name
    }
    done
 }
 myFunc "a b c d"

 还可以写成
 myFunc() {
     for i in $@ ; do {
         file1=$i
         shift
         echo $file1 is file name
     }
     done
 }
 myFunc "a b c d"

 myFunc() {
     for i in $@ ; do
     {
         file1=$i
         shift
         echo $file1 is file name
     } done
 }
 myFunc "a b c d"

利用函数返回值对比,而不采用中间变量
isFileSame $file1 $file2
if [ "$?" == "0" ]; then
    echo "file not same"
fi

完整的升级代码,实现对目录./下所有不同文件用vimdiff打开,相同文件跳过

十六、shell字符串正则表达式

1、采用grep来给搜索的内容上色


十七、shell字符串截取

假设有变量 var=http://www.aaa.com/123.htm

1. ‘#’ 号截取,删除左边字符,保留右边字符。

echo ${var#*//}

其中 var 是变量名,\# 号是运算符,*// 表示从左边开始删除第一个 // 号及左边的所有字符
即删除 http://
结果是 :www.aaa.com/123.htm

2. “##” 号截取,删除左边字符,保留右边字符。

`echo ${var##*/}`

##*/ 表示从左边开始删除最后(最右边)一个 / 号及左边的所有字符
即删除 http://www.aaa.com/

结果是 123.htm

3. “%”号截取,删除右边字符,保留左边字符

echo ${var%/*}

%/* 表示从右边开始,删除第一个 / 号及右边的字符
结果是:http://www.aaa.com

4. “%%” 号截取,删除右边字符,保留左边字符

echo ${var%%/*}

%%/* 表示从右边开始,删除最后(最左边)一个 / 号及右边的字符
结果是:http:

5. 从左边第几个字符开始,及字符的个数

echo ${var:0:5}
其中的 0 表示左边第一个字符开始,5 表示字符的总个数。
结果是:http:

6. 从左边第几个字符开始,一直到结束。

echo ${var:7}

其中的 7 表示左边第8个字符开始,一直到结束。
结果是 :www.aaa.com/123.htm

7. 从右边第几个字符开始,及字符的个数

echo ${var:0-7:3}

其中的 0-7(其实是负7)表示右边算起第七个字符开始,3 表示字符的个数。
结果是:123

8. 从右边第几个字符开始,一直到结束。

echo ${var:0-7}

表示从右边第七个字符开始,一直到结束。
结果是:123.htm

9、获取全路径中,文件名的方法:basename

# ls study/c/aa.c
study/c/aa.c

# basename study/c/aa.c
aa.c

十八、shell调试报错

1、括号不配对情况,关键字不配对情况

syntax error:unexpected end of file

十九、项目尝试自动删除多余的头文件。保证编译成功

>  但是此方案有一个缺陷,会造成某些重要函数声明丢失,进而引起严重故障,而且难查
>
> 优点:对于一个成熟项目,重复文件越少,检查越快,因为每个都是必不可少的引用,缺少则编译失败。而垃圾引用越多,检查就越慢,因为垃圾引用去除后,还要完整编译。不能增量编译
# 针对当前目录下所有源文件
list=`find ./ -name "*.c"`
for file_name in ${list}; do
    # 逐个文件提取 #include 的头文件行数。并将行号倒序,从大到小排列,
    # 方便从大到小行删除试错
    lines=`cat -n ${file_name} | grep '#include' | awk '{print $1}' | tac`
    tmp_content=''
    # 对单个 #include 进行尝试
    for line in ${lines}; do
        # 首先把这一行的 #include 内容原样保存,以防编译失败
        tmp_content=`sed -n "${line}p" ${file_name}`
        # 然后删除当前行的 #include 语句
        sed -i "${line}d" ${file_name}
        # 开始版本编译
        make rm
        if [ $? -eq 0 ]; then
            # 如果编译成功,则提示通过
            echo "pass"
        else
            # 如果编译失败,则将之前保存的当前 #include 内容插回到文件,将其恢复
            sed -i "${line} i${tmp_centent}" ${file_name}
        fi
     done
done

二十、采用sed实现的删除代码注释的功能(不够强大)

0、shell 基础判断

if [ -n ${str} ] 如果字符串长度大于 0
if [ -z ${str} ] 如果字符串长度等于 0
if [ str1 != str2 ]
if [ str1 == str2 ]

1、第一种实现方案

full_name=$1
dst_file=$2
if [ -z ${full_name} ]; then
    echo "no source file input"
    exit
fi

# 将完整路径以 / 分割,提取文件名,/home/path/abc.sh,则得到 abc.sh
short_name=`echo ${full_name} | tr -s '/' '[ ]' | awk '{print $NF}'`
# 把短文件名以'.' 分割,取第二段,即文件类型后缀,得到 sh
#`echo look.abc.sh.as.k | cut -f 1 -d '.'` ==>> look
#`echo look.abc.sh.as.k | cut -f 2 -d '.'`   ==>> abc
#`echo look.abc.sh.as.k | cut -f 3 -d '.'`   ==>> sh
post_fix=`echo ${short_name} | cut -f 2 -d '.' `

# 如果指定的接收注释后的文件名为空
if [ "${dst_file}" == "" ]; then
    #得到以'.'分割的完整路径的倒数第二段。若是 ./path/a.c 则得到 /path/a
    prefix=`echo ${full_name} | tr -s '[.]' '[ ]' | awk '{print $(NF - 1)}' `
    # 判断是否是以 / 开头
    first_char=`echo ${pre_fix}|grep ^\/`
    if [ -n ${first_char} ]; then
        #如果是以 / 开头,需要把 "."添加回去
        prefix="."${prefix}
    fi
    #得到一个临时文件
    target_file=${prefix}.temp
    touch ${target_file}
fi

case "${post_fix}" in
sh )
    sed "s/[[:space]]#.*//g" ${full_name} | sed '/^#/d' | \
    sed '/^[[:space:]]*$/g' | sed '/^$/d' > ${target_file}
    ;;
h | c | java )
    sed 's/\/\*.*\*\///g' ${full_name} | sed '/\/\*/,/.*\*\//d' | \
    sed 's/\/\/.*//g' | \
    sed '/s^[[:space:]]*$/d' | sed '/^$/d' > ${target_file}
    ;;
* )
   echo 'unknown file type'
   rm ${target_file}
esac

2、第二种实现方案

function delete_comment_file()
{
    file_name=$1
    #删除可能以空格开头的, //风格的注释
    sed -i '/^[ \t]*\/\//d' ${file_name}

    # 试图删除半路 // 风格的代码,但是保留例个,如代码中存在ftp://www.baidu.com 这种代码时
    # 但是实际效果没有保留成功
    sed -i 's/\/\/[^"]*//' ${file_name}

    # 删除C语言最基本的单行注释 /* assd */
    sed -i 's/\/\*.*\*\///' ${file_name}

    # 删除C语言块注释
    # /* adi
    # adf */
    sed -i 's/^[ \t]*\/\*,/.*\*\//d' ${file_name}
}

function delete_comment()
{
    for file in `ls`; do
        case ${file} in
          *.c )
              delete_comment_file ${file} ;;
          *.cpp )
              delete_comment_file ${file} ;;
          *.h )
              delete_comment_file ${file} ;;
          * )
              if [ -d ${file} ]; then
                 cd ${file}; delete_comment; cd ..
              fi ;;
         esac
     done
}

my_dir=$1
if [ ! -e ${my_dir} ]; then
    echo "empty dir"
else
    cd ${my_dir}
    delete_comment
fi

二十、数组

1、数组定义

[root@localhost shell]$foo=(a b c)
[root@localhost shell]$echo ${foo[@]}
a b c
[root@localhost shell]$echo ${foo[0]}
a
[root@localhost shell]$echo ${foo[1]}
b
[root@localhost shell]$echo ${foo[2]}
c

2、数组追加元素

[root@localhost shell]$foo+=(d e f)
[root@localhost shell]$echo ${foo[5]}
f
[root@localhost shell]$echo ${foo[@]}
a b c d e f
[root@localhost shell]$echo ${foo[*]}
a b c d e f
[root@localhost shell]$echo ${#foo[*]}
6
[root@localhost shell]$echo ${#foo}
1
[root@localhost shell]$echo ${foo}
a
[root@localhost shell]$echo ${#foo[*]}
6
[root@localhost shell]$echo ${#foo[1]}
1
[root@localhost shell]$echo ${#foo[6]}
0
[root@localhost shell]$echo ${#foo[2]}
1
[root@localhost shell]$echo ${#foo[@]}
6

3、数组用法,引用元素和全部数组

[root@localhost shell]$echo ${foo[5]}
f
[root@localhost shell]$echo ${foo[@]}
a b c d e f
[root@localhost shell]$echo ${foo[*]}
a b c d e f

4、数组长度

[root@localhost shell]$echo ${#foo[*]}
6
[root@localhost shell]$echo ${#foo[1]}
1
[root@localhost shell]$echo ${#foo[6]}
0
[root@localhost shell]$echo ${#foo[2]}
1
[root@localhost shell]$echo ${#foo[@]}
6

5、数组所有元素,两种表示方法{foo[@]}和{foo[*]}的区别

[root@localhost study]$echo "${foo[@]}"
a b c d e f
[root@localhost study]$for i in "${foo[@]}"; do echo $i;done
a
b
c
d
e
f
[root@localhost study]$ for i in "${foo[*]}"; do echo $i; done
a b c d e f
[root@localhost study]$

6、数组的删除

[root@localhost study]$echo ${foo[@]}
a b c d e f
[root@localhost study]$unset foo
[root@localhost study]$echo ${foo[@]}
[root@localhost study]$

二十一、ls的乐趣

1、同时查看多个目录

[root@localhost study]$ls c/ tmp/
c/:
a.out       ftp          gtest-1.7.0.zip  option    ss.c            tt.c
find_ip     ftp2         hello.c          option.c  test_readdir    webserver
find_ip.c   ftp3         ip_result        pipe.c    test_readdir.c
find_ip.sh  gtest-1.7.0  massif.out.7238  ss.b      tt

tmp/:
wine-3.0.tar.xz
[root@localhost study]$

2、长格式查看

[root@localhost study]$ls c -l
total 1324
-rwxrwxrwx.  1 wishcell wishcell    8824 Jun 18  2018 a.out
-rwxrwxr-x.  1 wishcell wishcell    8560 Nov 24 14:01 find_ip
-rw-rw-r--.  1 wishcell wishcell     150 Nov 24 14:01 find_ip.c
-rw-rw-r--.  1 wishcell wishcell    6194 Nov 24 14:02 find_ip.sh
drwxrwxrwx.  4 wishcell wishcell      34 Jun 19  2018 ftp
drwxrwxrwx.  3 wishcell wishcell      17 Jun 19  2018 ftp2
[root@localhost study]$
关键字说明:
[root@localhost study]$ls c -l
total 1324
-rwxrwxrwx.  1 wishcell wishcell    8824 Jun 18  2018 a.out
-rwxrwxr-x.  1 wishcell wishcell    8560 Nov 24 14:01 find_ip
-rw-rw-r--.  1 wishcell wishcell     150 Nov 24 14:01 find_ip.c
-rw-rw-r--.  1 wishcell wishcell    6194 Nov 24 14:02 find_ip.sh
drwxrwxrwx.  4 wishcell wishcell      34 Jun 19  2018 ftp
drwxrwxrwx.  3 wishcell wishcell      17 Jun 19  2018 ftp2

第一列第一个字符,表示类型。
    `-`表示普通文件
    `d`表示目录
    `l`表示链接

二十二、cp/mv查漏补缺

1、-r参数,表示递归

2、-u参数,表示只复制目标文件夹中不存在的文件

3、-i参数,提醒用户确认,是否覆盖f

二十三、alias命令

1、查看系统中的别名

[wishcell@localhost tmp]$ alias
alias docker-machine='__docker_machine_wrapper'
alias egrep='egrep --color=auto'
alias fgrep='fgrep --color=auto'
alias grep='grep --color=auto'
alias l.='ls -d .* --color=auto'
alias ll='ls -l --color=auto'
alias ls='ls --color=auto'
alias vi='vim'
alias which='alias | /usr/bin/which --tty-only --read-alias --show-dot --show-tilde'

2、自己定义一个别名

[wishcell@localhost tmp]$ alias foo='cd /usr;ls;cd -'
[wishcell@localhost tmp]$ foo
bin  games    lib    libexec  sbin   src
etc  include  lib64  local    share  tmp
/home/wishcell/study/tmp
[wishcell@localhost tmp]$
[wishcell@localhost tmp]$ type foo
foo is aliased to `cd /usr;ls;cd -`
[wishcell@localhost tmp]$

3、删除别名

[wishcell@localhost tmp]$ unalias foo
[wishcell@localhost tmp]$ type foo
-bash: type: foo: not found
[wishcell@localhost tmp]$

二十四、head/tail命令

1、默认指定文件的前/后 10行

首先创建一个101行的文本文件:
[wishcell@localhost dir2]$ echo -e {1..100}"\n" >b.txt

确认文件行数:
[wishcell@localhost dir2]$ cat b.txt | wc -l
101

[wishcell@localhost dir2]$ head b.txt
 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
[wishcell@localhost dir2]$ tail b.txt
 92
 93
 94
 95
 96
 97
 98
 99
 100

2、-n参数指定行数

[wishcell@localhost dir2]$ head -n 5 b.txt
 1
 2
 3
 4
 5
[wishcell@localhost dir2]$ tail -n 5 b.txt
 97
 98
 99
 100

[wishcell@localhost dir2]$

二十五、花括号{}扩展

1、基础示例

[wishcell@localhost dir2]$ echo front_{A,B,C}_back
front_A_back front_B_back front_C_back
[wishcell@localhost dir2]$

2、数字展开

[wishcell@localhost dir2]$ echo number_{1..5}
number_1 number_2 number_3 number_4 number_5
[wishcell@localhost dir2]$

3、字母展开

[wishcell@localhost dir2]$ echo {Z..A}
Z Y X W V U T S R Q P O N M L K J I H G F E D C B A
[wishcell@localhost dir2]$

4、复合展开

[wishcell@localhost dir2]$ echo a{A{1,2},B{3,4}}b
aA1b aA2b aB3b aB4b
[wishcell@localhost dir2]$

二十六、linux 命令行,光标控制

1、ctrl+A 光标移到行首

2、ctrl+E光标移到行尾

3、ctrl+L清除屏幕,保留当前行

二十七、程序由后台转到前台执行

1、示例

[wishcell@localhost dir2]$ vim &
[1] 29650
[wishcell@localhost dir2]$ fg vim
vim
或者执行 bg %1 也能达到同样效果。但是对后面数字没有把握

二十八、uniq命令

1、显示重复的内容

[wishcell@localhost dir2]$ cat b.txt
line 1
line 2
line 2
line 3
line 3
line 4
line 5
[wishcell@localhost dir2]$ uniq -d b.txt
line 2
line 3
[wishcell@localhost dir2]$

2、显示不重复的内容,-c参数可以看到行重复的次数

[wishcell@localhost dir2]$ uniq b.txt
line 1
line 2
line 3
line 4
line 5
[wishcell@localhost dir2]$

[wishcell@localhost dir2]$ uniq b.txt -c
      1 line 1
      2 line 2
      2 line 3
      1 line 4
      1 line 5
[wishcell@localhost dir2]$

二十九、shell printfC语言十分相似

[wishcell@localhost dir2]$ printf "%d,%f,%o,%s,%x,%X\n" 380 380 380 380 380 380
380,380.000000,574,380,17c,17C
[wishcell@localhost dir2]$

带上类似C语言的控制字符.
[wishcell@localhost dir2]$ printf "%05d,%05.5f,%o,%s,%#x,%#X\n" 380 380 380 380 380 380
00380,380.00000,574,380,0x17c,0X17C

三十、 shell here 文档

[wishcell@localhost shell]$ cat ad.sh
report_uptime(){
        cat <<- _EOF_
            <H2> System Uptime</H2>
            <PRE>$(df -h; uptime)</PRE>
_EOF_
}
report_uptime
[wishcell@localhost shell]$

[wishcell@localhost shell]$ sh ad.sh
            <H2> System Uptime</H2>
            <PRE>Filesystem               Size  Used Avail Use% Mounted on
/dev/mapper/centos-root   41G   27G   15G  64% /
devtmpfs                 905M     0  905M   0% /dev
tmpfs                    920M     0  920M   0% /dev/shm
tmpfs                    920M  8.9M  911M   1% /run
tmpfs                    920M     0  920M   0% /sys/fs/cgroup
/dev/sda1               1014M  179M  836M  18% /boot
/dev/mapper/centos-home   20G  2.2G   18G  11% /home
tmpfs                    184M   32K  184M   1% /run/user/1000
 23:47:55 up 13:29,  5 users,  load average: 0.08, 0.03, 0.05</PRE>

三十一、常用条件表达式

1、文件比较

file1 -ef file2    file1 和 file2拥有相同的信息节点和编号(这两个文件通过硬链接指向同一个文件)
file1 -nt file2    file1 比 file2 新
file1 -ot file2    file1 比 file2 旧
-b file            file 存在而且是一个块(设备)文件
-c file            file 存在而且是一个字符(设备)文件
-d file            file 存在而且是一个目录
-e file            file 存在
-f file            file 存在而且是普通文件
...
-L file            file 存在而且是一个符号链接
...
-r file            file 存在而且可读
-s file            file 存在并且长度大于0
...
-w file            file 存在且可定
-x file            file 存在且可执行

2、字符串比较

[ string ]         string 不为空
-n string          长度大于0
-z strig           长度等于0
string1=string2
string1==string2
string1!=string2
string1>string2   排序时,string1 在 string2 之后 。实际使用时,">","<"都需要斜杠转意
string1<string2

3、整数比较

a -eq b   相等
a -ne b   不等
a -le b   小于等于
a -lt b   小于
a -ge b   大于等于
a -gt b   大于

shell if [[]]/[]/||/&&

https://www.cnblogs.com/aaron-agu/p/5700650.html

三十二、case语句

case $(var) in
   a)              echo '关键字为 a 则成立'     ;;
   [[:alpha]] )    echo '关键字为单个字母则吻合' ;;
   ???)            echo '关键字为三个字符则吻合' ;;
   *.txt)          echo '以 .txt 结尾则吻合'    ;;
   *)              echo '其余条件吻合'          ;;
esac

三十三、强大的搜索应用

1、shell搜索函数在哪个文件中定义

# 比如要搜索 sleep 函数在当前目录哪个文件中定义:
[wishcell@localhost shell]$ cat aa.sh
funName=sleep
grep -rn "\w\+[ ]\+${funName}([^()]*)[ ]*{\?$" ./

# 带调度信息的调用:
[wishcell@localhost shell]$ sh -x aa.sh
+ funName=sleep
+ grep -rn '\w\+[ ]\+sleep([^()]*)[ ]*{\?$' ./
./monitor.cc:305:void sleep()
[wishcell@localhost shell]$

#不带调度信息的调用:
[wishcell@localhost shell]$ sh aa.sh
./monitor.cc:305:void sleep()
[wishcell@localhost shell]$

2、对文件进行优化,仅搜索 C文件,加快搜索速度

cat file.txt | xargs -i grep -rl {} ../bami/ -type f | grep \.c$
说明: -rl  只显示文件名
       -rni既有文件名,又有函数名

3、利用正则表达式,由 C 直接生成头文件(此例完美工作)

[wishcell@localhost shell]$ cat get_head.sh
#!/bin/bash
source_file=$1
if [[ -f ${source_file} ]]; then
    grep "\w\+[ ]\+\w\+([^()]*)[ ]*{\?$" ${source_file} | \
    grep -v "main" | sed -e 's/{\?$/;/' > ${source_file%.*}.h
fi

# 注释:
#   \w\+ 表示多个字符,至少一个,这是函数的返回值类型
#   [ ]\+ 表示多个空格,至少一个
#   ([^()*]) 这个没有理解
#       ==>>>>> [^()]*表示不包含左右小括号(、)的任意字符
#       ==>>>>> 最外层的()是函数体的格式,函数名后面的小括号。
#       ==>>>>> 就是函数定义的括号内,不能再有括号, 非常正确
#   [ ]* 表示任意多个空格,可以是0个
#   {\?$ 表示以0个或者一个 '{' 结束,
#      就是说函数定义的行尾是否有半个花括号
#   % 符号把匹配成功的东西丢弃掉,这里显然是把文件的后缀丢掉,
#      然后重新添加后缀 .h

4、利用正则表达式,对 lcov 产生的网页操作

进行函数提取,可以感知函数是否被覆盖
[wishcell@localhost c]$ grep -o "[0-9]\+[ ]\+:[ ]\+\w\+[ ]\+\w\+([^()]*)[ ]*{\?" a.c.gcov.html
1 : int add(int a, int b)
1 : int sub(int a, int b)
1 : int main()
[wishcell@localhost c]$

# 故意把 C 函数的花括号放在函数定义的地方,重新实验,效果仍然满意
[wishcell@localhost c]$ grep -o "[0-9]\+[ ]\+:[ ]\+\w\+[ ]\+\w\+([^()]*)[ ]*{\?" a.c.gcov.html
1 : int add(int a, int b){
1 : int sub(int a, int b){
1 : int main() {
[wishcell@localhost c]$

综合应用

1、一个编译脚本,有各种场景

#!/bin/bash
currDir=`pwd`
# 利用项目名称,进行字符串分割,保留项目所在路径
prjHome=${currDir%%prjName*}

buildFlag=$1
if [ -z $buildFlag ]; then
    buildFlag="help"
fi

# $1 is color, and $2 is content string
function colorPrint(){
    stringContent=$2
    if [ "$1" == "red" ]; then
        echo -e "\033[41m $stringContent \033[\m"
    elif [ "$1" == "green" ]; then
        echo -e "\033[42m $stringContent \033[\m"
    elif [ "$1" == "yellow" ]; then
        echo -e "\033[43m $stringContent \033[\m"
    elif [ "$1" == "gray" ]; then
        echo -e "\033[47m $stringContent \033[\m"
    fi
}
function quitBecauseFail() {
    if [ "$1" == "0" ]; then
        colorPrint green "$2 build succeed!"
    else
        colorPrint red   "$2 build failed!"
    fi
}

if [ "${buildFlag}" == "help" ]; then
    set -v
    echo "usage: sh buildPrjUT.sh"
    echo "       sh buildPrjUT.sh gdb"
    echo "       sh buildPrjUT.sh run"
    echo "       sh -x buildPrjUT.sh run"
    echo "       export PS4='+\${LINENO:\${FUNCNAME[0]}}-->>  '"
    echo "       date>mytime; sh buildPrjUT.sh flag 2>&1 | tee myLog; date>>mytime"
    set +v
elif [ "${buildFlag}" == "all" ] || [ "${buildFlag}" == "All" ] ; then
    set -v
    buildFlag="all"
    echo "para is ${buildFlag}"
    set +v
elif [ "${buildFlag}" == "gdb" ] || [ "${buildFlag}" == "GDB" ] ; then
    set -v
    buildFlag="gdb"
    echo "para is ${buildFlag}"
    set +v
else [ "${buildFlag}" == "gdb" ] || [ "${buildFlag}" == "GDB" ] ; then
    set -v
    buildFlag="gdb"
    echo "para is ${buildFlag}"
    set +v
fi

cpuNum=$((`grep -c "processor" /proc/cpuinfo` - 1))
echo "cpuNum is only : ${cpuNum}"

buildPath=${prjHome}/buildDir
destFile=${prjHome}/build/tt
toBeChange=${prjHome}/source/src3/aa.c
excludeDir=(${prjHome}/source/src1 \
            ${prjHome}/source/src2)

if [ "${buildFlag}" == "run" ]; then
    if [ -e "${destFile}" ]; then
        ${destFile}  # 如果目标文件已经生成,直接运行
    else
        colorPrint 'red' "${destFile} not exist" # 否则报错
    fi
elif [ "${buildFlag}" == "gdb" ]; then
    if [ -e "${destFile}" ]; then
        gdb ${destFile}  # 如果目标文件已经生成,直接运行
    else
        colorPrint 'red' "${destFile} not exist" # 否则报错
    fi
fi

echo currDir=${currDir}
echo buildPath=${buildPath}
echo toBeChange=${toBeChange}
echo "exclude dir Array:"
for i in "${excludeDir[@]}"; do colorPrint yellow "  ${i}";done

set -v
# 将此文件 30~40 行删除
cd $(dirname ${toBeChange}) && git checkout $(basename ${toBeChange}) && sed -i '30,40d' $(basename ${toBeChange})
# 在此文件某关键字处,其后追加几行内容,先找到关键字所在行
lineNum=`sed -n '/keyWord/= ' ${toBeChange}` \
echo ${lineNum}
sed -i "${lineNum}aThis\ is \ New\ content\ string1" ${toBeChange}
sed -i "${lineNum}aThis\ is \ New\ content\ string2" ${toBeChange}

# 删除多余文件,排除干扰
for i in "${excludeDir[@]}"; do rm ${i} -rf; done
set +v

if [ "${buildFlag}" == "all" ]; then
    make all
fi

if [ "${buildFlag}" != "all" ]; then
    echo "now is building module: ${buildFlag}"
    cd ${buildPath}
    if [ "${buildFlag}" == "module_1" ]; then
        make module_1
        quitBecauseFail $? ${buildFlag}
    elif [ "${buildFlag}" == "module_2" ]; then
        make module_2
        quitBecauseFail $? ${buildFlag}
    fi
fi

rm ${destFile} -rf
cd ${buildPath} && make all
quitBecauseFail $? ${buildFlag}

if [ -e ${destFile} ] ; then
    # 恢复之前临时修改的文件
    cd $(dirname ${toBeChange}) && git checkout $(basename ${toBeChange})
    for i in "${excludeDir[@]}"; do cd $(dirname ${i}) && git checkout $(basename ${i})"; done

    echo "${destFile} build succeed!"
    ${destFile}
else
    colorPrint gray "${targetFile} build failed"
    colorPrint red  "${targetFile} build failed"
fi

shell数组倒序打印

一、方法一:使用字符串切片

#! /bin/bash
read -p "请输入倒序内容:" str
len=${#str} # 获取字符串长度
for((i=$len;i>=0;i--))
do
        echo -e "${str:$i:1}\c"  # 其中 -e 是开启转义 \c 取消echo的换行
done
echo ""

二、第二种:利用shell中的数组

#! /bin/bash
read -p "请输入倒序内容:" s
str=`echo $s | sed 's/./& /g'` # 这里将字符串转化为数组格式 h e l l e v e r y o n e
array=($str)    # 将字符串转化为数组(h e l l e v e r y o n e)
len=${#array[@]}    # 获取数组长度 还有一种方式 len=${#array[*]}
for ((i=$len - 1;i>=0;i--))
do
        echo -e "${array[$i]}\c" # 其中 -e 是开启转义 \c 取消echo的换行
done
echo ""

特定场景:将关键字前面的内容复制到其后面

1、需求说明,对于跳转代码的情形

A、无返回值的情况

程序中,存在如下代码:
{
   ...
   return;
   //----可能存在若干空行
ERR:
    return;
}

B、返回变量

程序中,存在如下代码:
{
   ...
   return ret;
   //----可能存在若干空行
ERR:
    a=3;
    return ret;
}

C、无返常数

程序中,存在如下代码:
{
   ...
   return OK;
   //----可能存在若干空行
ERR:
    printf("error...\n")
    return OK;
}

2、现在需要只要遇到 ERR: 就立即返回错误

`但是不能一概而论,直接写死返回某种形式,因为这样可能造成返回值错误,从而编译失败
理想做法,是根据规律,将 "ERR:"上面的一行返回值语句复制到它下面`

3、解决思路

`1、将 ERR: 之前,可能的空行删除,确保其上紧接的是一条 return 语句
 2、利用 sed 工具,获取所有 ERR: 所在行的行号数组
 3、将 shell数组倒序处理
    3.1 利用 sed -n 3p file 语法,将上一行字符串获取到
    3.2 利用 sed -i "3istr" 的方法,将 str 追加到指定行后`

4、具体实现代码

1、删除所有可能的空行(更多的删除方法,参看sed.html)
sed -i "/^[[:space:]]*$/d" ${fileName}

2、利用 sed 工具,获取所有 ERR: 所在行的行号数组
lineNum=`sed -n /ERR:/=` ${fileName}
lineNumArray=(${lineNum}) # 其实即使不这么操作,${lineNum} 自身还是数组
len=${#lineNumArray[@]}   #数组长度还有一种表示方法:${#lineNumArray[*]}

3、利用循环语句,对各个行分别处理
for ((i=$len - 1; i>=0; i--))
do
     # 获取 `ERR:` 所在行前一行的内容
     pre_line_content=`sed -n $((${lineNumArray[$i]}-1))`p  ${fileName}
     echo "pre_line_content=${pre_line_content}
     # 将 ERR: 前一行的内容,追加到其后
     sed -i "${lineNumArray[$i]}a${pre_line_content}"  ${fileName}
done

大段代码注入到文件中

1、基础用法

cat <<- MyEndFlag  > index.html
<html>
<body> this is a tst </body>
</html>
MyEndFlag

其中,MyEndFlag是自己定义的一个开始结束语标志,当然一般规范的写法,都是采取 END 或者EOF.

[Administrator.WINDOWS-LGJ801D] ➤ sh inject.sh
[Administrator.WINDOWS-LGJ801D] ➤ ls
index.html  inject.sh
[Administrator.WINDOWS-LGJ801D] ➤ cat index.html
<html>
<body> this is a tst </body>
</html>

2、追加内容到指定文件

只要将重定向符号改为追加即可:
cat <<- MyEndFlag  >> index.html

3、空行也直接原样追加,超级省心

[Administrator.WINDOWS-LGJ801D] ➤ cat index.html

<html>
<body> this is a tst </body>
</html>

this is the end

4、此功能对项目工程的应用价值

   可以将某段代码直接写入功能代码,毕竟这样写出来的测试用例,编译成本最低。
否则需要解决大量的头文件引用问题,数据结构重定义,等等十分繁琐的事情。
   用完之后,清理也十分简单,因为在注入时,脏代码前面都加上了标志。

function hasInjected(){
    fileName=$1
    injectFlag=`sed -n "/DIRTY_CODE_BEGIN/=" ${fileName}`
    if [[ "${injectFlag}" =="" ]]; then
        echo "${fileName} has not inject testcase"
        return 0
    else
        echo "${fileName} has inject testcase"
        return 1
    fi
}

function injectTesting(){
fileName=$1
# judge if dirty code has injected
hasInjected $fileName
if [[ "$?" =="1"]]; then
    return  # if code has injected, return directively
fi

cat <<- MyEndFlag  >> ${fileName}
#define DIRTY_CODE_BEGIN
void dirTest()
{
    int para = 3;
    funcAtest(para);
}
MyEndFlag
}

#using it like this
injectTesting  ${projectHome}/module/funcA.c

5、shell整块输出到屏幕,而不是输出到文件

cat <<- MyEndFlag
#define DIRTY_CODE_BEGIN
void dirTest()
{
    int para = 3;
    funcAtest(para);
}
MyEndFlag
}

6、既输出到文件,又打印到屏幕

cat <<- MyEndFlag | tee ${fileName}
#define DIRTY_CODE_BEGIN
void dirTest()
{
    int para = 3;
    funcAtest(para);
}
MyEndFlag
}

shell脚本延时

秒级延时 : sleep 1
延时 10ms: sleep 10
延时 10ms: usleep 1000

一键恢复 git 改动

git status | grep 删除 | awk '{print $3}' | egrep -o ".*/testcase" | \
grep -vw key_1/module/testcase | grep -vw key_2/module/testcase | \
xargs -i git checkout {}

注意事项:
uniq -d 这个选项不能加,加了会达不到目标,就多执行几次 git checkout 吧
grep -vw key1 是因为怕有其它项目,名称为 xxkey1/module/testcase 也被过滤掉
egrep -o ".*/testcase/" 效果不同于 egrep -o .*/testcase/
  后一种效果只能匹配到两个字符,前一种是正则表达式匹配所有,这个坑了我一段时间

shell 数组使用,坑

lineNum=`sed -n "/${keyWord}/="` ${fileName}
这里, ${keyword} 外面只能用双引号,一旦用了单引号,结果就会错

结果 clineNum 可能不止一条记录,如果是多条,就需要用数组来存储
lineNumArray=(${lineNum})

如果没有找到关键字,则记录为空,所以需要判断
if [ ${#lineNumArray[@]} == 0]; then
    echo key ${keyword} not found from ${fileName}
else
    # 将关键字开始,到文件尾的内容全部删除
    sed -i "${lineNumArray[0]},$"d ${fileName}
fi

awk对指定列编辑

file.txt :
35 | a1   | keyString:a1
36 | a1b  | keyString:a1b
37 | ca1  | keyString:ca1

现在想将文件转化为csv格式,即把第二列改为','即可
awk '{if(i=2)$i=",";print}' file.txt >file.csv

用sed对文件进行字符串替换:
sed -i s/'| keyString:'/','/g file.csv

替换之后,再搜索,确定修改成功:
sed -n /'| keyString:'/p file.csv

发表评论