分类目录归档:未分类

shell

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

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 选项执行脚本,就能得到第一句执行的行号及函数名

<pre><code class="">## 5、调试技巧set -v
“`c
在每次执行语句时,希望看到执行的具体内容,当然采用 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、脚本样例

八、强制执行 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=? ==>> 正确写法

<pre><code class="">### ii、判断语句,中括号与变量之间,必须有空格,否则报错
“`c
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=

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下有许多小文件夹。以上脚本不够强大,无法支持。

<blockquote>
<h4>现在希望,在<code>./root</code>下执行脚本,能把<code>root/dir1/source/a.c</code>,<code>root/dir1/include/a.h</code>以及<code>./root/dir2/source/b.c</code>,<code>./root/dir2/include/b.h</code> 都列举出来,并采用 <code>vimdiff</code>打开。</h4>

<h4>如果<code>./root/dir2/source/a.c</code>不存在,则将<code>./root/dir1/source/a.c</code>删除</h4>
</blockquote>

<pre><code class="">### B、按照BDD设计思想,先把产品用法列出,列出实例化用例
“`c
> 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来给搜索的内容上色

grep -rni xxx ./ --color

十七、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 printf与C语言十分相似

[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   大于

三十二、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([^()]*)[ ]*{\?

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

\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中内容的样子

####D、脚本的样子

十七、shell字符串截取

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

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

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

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

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

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

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

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

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

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

Linux服务器安全之 fail2ban的安装与配置

缘起

作为一个程序员,自然知道服务器的安全性,知道总有好事这人,喜欢无聊地扫描别人的端口。
  因忙于其它功能,只是将密码设置的稍微复杂了点。今晚登录,发现界面提示,刚刚有一波登录尝试,显然有人想暴力破解我的密码。
    一方面,立即对网站进行备份,重要数据复制到备份盘
   另一方面,安全问题不得不立即解决了,于是问了一下度娘,找到如下两篇文章

一、Linux服务器(CentOS6)安全之 fail2ban的安装与配置

[文章来源](https://www.cnblogs.com/bestOPS/p/10616401.html)

因本人服务器是CentOS7.8,尽管这里讲的是针对 nginx 的配置,而本人服务器是基于 docker lamp 的 wordpress 环境。但是本人比较习惯使用 nginx 系统,所以这套配置流程,就抄录过来。
  原作者如有意见请留言,本人将立即清除

查看Nginx的访问日志中,发现了大量404请求,仔细研究一番发现有人正在试图爆破网站。

于是乎 --- 查资料后得知有一个神奇的工具 fail2ban 可以配合 iptables 自动封IP
iptables 是Centos6 的一种防火墙策略,在Centos7 中已被 Firewall所取代.
本次实验系统为Centos6

1、下载并安装fail2ban

cd /usr/loca/src
 wget https://github.com/fail2ban/fail2ban/archive/0.9.4.tar.gz
 tar -zxf 0.9.4.tar.gz
 mv 0.9.4 fail2ban-094
 cd fail2ban-094
 ./setup.py

2. 目录结构

/etc/fail2ban                    # fail2ban 服务配置目录
/etc/fail2ban/action.d           # iptables 、mail 等动作文件目录
/etc/fail2ban/filter.d           # 条件匹配文件目录,过滤日志关键内容
/etc/fail2ban/jail.conf          # fail2ban 防护配置文件
/etc/fail2ban/fail2ban.conf      # fail2ban 配置文件,定义日志级别、日志、sock 文件位置等

cd /etc/fail2ban                 # 进入配置文件所在的目录
cp jail.conf jail.local          # 备份配置文件

3、 配置防护

vim /etc/fail2ban/filter.d/nginx.conf

[Definition]
failregex = <HOST> -.*- .*HTTP/1.* 404 .*$
ignoreregex =


保存后测试配置文件相对于日志是否正确

fail2ban-regex /home/wwwlogs/access.log /etc/fail2ban/filter.d/nginx.conf 


然后将封禁规则加入配置文件

vim /etc/fail2ban/jail.conf   # 文件尾部写入

[nginx]
enabled = true
port = http,https
filter = nginx                          # 规则文件的名字
action = iptables[name=nginx, port=http, protocol=tcp]
        # sendmail-whois[name=tomcat, dest=abc@mail.com] 发送邮件功能
logpath = /home/wwwlogs/access.log     # 日志路径
bantime = 14400                           # 封禁的时间
findtime = 3                          # 在几秒内
maxretry = 2                           # 有几次

4、 启动

/etc/init.d/iptables start
/etc/init.d/fail2ban start 或 fail2ban-client start

5、 查看状态等命令

fail2ban-client start # 启动
fail2ban-client reload # 重载
fail2ban-client stop # 停止
fail2ban-client status # 查看状态
fail2ban-client set nginx addignoreip 180.158.35.30 # 将ip加入nginx监狱的白名单
fail2ban-client set nginx delignoreip 180.158.35.30 # 将ip移除nginx监狱的白名单
fail2ban-client set nginx banip IP地址 # 手工ban
fail2ban-client set nginx unbanip IP地址 # 手工解

6. 查看fail2ban的日志

fail2ban的日志默认在 /var/log/fail2ban.log
日志中记录着Ban IP的记录和UnBan IP的记录, 也有修改配置文件重载后的记录
cat /var/log/fail2ban

7、 小结

fail2ban 还有许多强大的功能待研究,在 /etc/fail2ban/filter.d 中,有许多模板可以学习并使用
虽说把非法请求封禁一段时间看似安全些,但只要有对外的端口就不能保证服务器的绝对安全。
多了解不同工具的组合使用,才会使服务器更加安全。

二、Linux 使用fail2ban+Firewalld来阻止恶意IP,提高服务器安全。

文章来源

CentOS 7安装fail2ban+Firewalld防止SSH爆破与CC攻击说明:
   差不多很多博主都会遇到被CC攻击和SSH爆破的情况,这个时候就需要做下防御措施了,
   前几天发过一个防CC脚本,参考: https://www.moerats.com/archives/484/ ,不过对于CentOS 7来说,
   由于防火墙的因素,配置起来还是有点麻烦的,这里争对CentOS 7再分享个简单防CC攻击和SSH爆破的方法。

0、前言

fail2ban可以监视你的系统日志,然后匹配日志的错误信息执行相应的屏蔽动作。
网上大部分教程都是关于fail2ban+iptables组合,考虑到CentOS 7已经自带Firewalld,所以这里我们也可以利用fail2ban+Firewalld来防CC攻击和SSH爆破。
本教程编辑文件使用vi命令,如果不会用的,可以使用比较简单的nano,可以参考: https://www.moerats.com/archives/485/ ,或者使用FTP工具,如WinSCP登录VPS操作。

准备工作

1、检查Firewalld是否启用

#如果您已经安装iptables建议先关闭 service iptables stop 
#查看Firewalld状态 firewall-cmd --state
#启动firewalld systemctl start firewalld 
#设置开机启动 systemctl enable firewalld.service

启用Firewalld后会禁止所有端口连接,因此请务必放行常用的端口,以免被阻挡在外,以下是放行SSH端口(22)示例,供参考:
#放行22端口 firewall-cmd --zone=public --add-port=80/tcp --permanent 
#重载配置 firewall-cmd --reload
#查看已放行端口 firewall-cmd --zone=public --list-ports

2、安装fail2ban

fail2ban可以监控系统日志,并且根据一定规则匹配异常IP后使用Firewalld将其屏蔽,尤其是针对一些爆破/扫描等非常有效。
# CentOS内置源并未包含fail2ban,需要先安装epel源 yum -y install epel-release #安装fial2ban yum -y install fail2ban
安装成功后fail2ban配置文件位于/etc/fail2ban,其中jail.conf为主配置文件,相关的匹配规则位于filter.d目录,其它目录/文件一般很少用到,如果需要详细了解可自行搜索。

3、配置规则

新建jail.local来覆盖fail2ban的一些默认规则:
#新建配置 vim /etc/fail2ban/jail.local
#默认配置 
[DEFAULT] ignoreip = 127.0.0.1/8
    bantime = 86400 
    findtime = 600 
    maxretry = 5 
    #这里banaction必须用firewallcmd-ipset,这是fiewalll支持的关键,如果是用Iptables请不要这样填写 banaction = firewallcmd-ipset action = %(action_mwl)s
参数说明:
ignoreip:IP白名单,白名单中的IP不会屏蔽,可填写多个以(,)分隔 
bantime:屏蔽时间,单位为秒(s) 
findtime:时间范围 
maxretry:最大次数 banaction:屏蔽IP所使用的方法,上面使用firewalld屏蔽端口

4、防止SSH爆破

如果您还在使用默认SSH端口(22),可能每天都会被扫描,我们可以修改端口尽量避免被扫,
参考: https://www.moerats.com/archives/394/ ,或者可以使用fail2ban将恶意IP屏蔽。
继续修改jail.local这个配置文件,在后面追加如下内容:
[sshd]
enabled = true 
filter = sshd 
port = 22 
action = %(action_mwl)s 
logpath = /var/log/secure
参数说明:
[sshd]:名称,可以随便填写 
filter:规则名称,必须填写位于filter.d目录里面的规则,sshd是fail2ban内置规则 
port:对应的端口 
action:采取的行动 
logpath:需要监视的日志路径

到这一步,我们jail.local的规则看起来可能像下面这样子:
[DEFAULT]
ignoreip = 127.0.0.1/8
bantime = 86400
findtime = 600
maxretry = 5
banaction = firewallcmd-ipset
action = %(action_mwl)s

[sshd]
enabled = true
filter = sshd
port = 22
action = %(action_mwl)s
logpath = /var/log/secure
上面的配置意思是如果同一个IP,在10分钟内,如果连续超过5次错误,则使用Firewalld将他IP ban了。输入systemctl start fail2ban 启动fail2ban来试试效果。
使用另一台服务器不断尝试连接SSH,并且不断的将密码输入错误,你会发现连续超过5次后直接连不上,说明IP被ban了,可以输入:fail2ban-client status sshd查看被ban的IP,如下截图。

防止CC攻击

这里仅以Nginx为例,使用fail2ban来监视nginx日志,匹配短时间内频繁请求的IP,并使用firewalld将其IP屏蔽,达到CC防护的作用。
#需要先新建一个nginx日志匹配规则
vi /etc/fail2ban/filter.d/nginx-cc.conf
#填写如下内容
[Definition]
failregex = -.*- .*HTTP/1.* .* .*$ ignoreregex =
继续修改jail.local追加如下内容:
[nginx-cc]
enabled = true
    port = http,https
    filter = nginx-cc
    action = %(action_mwl)s
    maxretry = 20
    findtime = 60
    bantime = 3600
    logpath = /usr/local/nginx/logs/access.log
上面的配置意思是如果在60s内,同一IP达到20次请求,则将其IP ban 1小时,上面只是为了测试,请根据自己的实际情况修改。logpath为nginx日志路径。
防止Wordpress爆破
如果您经常分析日志会发现有大量机器人在扫描wordpress登录页面wp-login.php,虽然对方可能没成功,但是为了避免万一还是将他IP干掉为好。
#需要先新建一个nginx日志匹配规则
vi /etc/fail2ban/filter.d/wordpress.conf
#填写如下内容
[Definition] failregex = ^ -.* /wp-login.php.* HTTP/1\.." ignoreregex =
继续修改jail.local追加如下内容:
[wordpress] enabled = true port = http,https filter = wordpress action = %(action_mwl)s maxretry = 20 findtime = 60 bantime = 3600 logpath = /usr/local/nginx/logs/access.log
当然,别忘记输入systemctl restart fail2ban重启fail2ban使其生效。
常用命令
#启动 systemctl start fail2ban
#停止 systemctl stop fail2ban
#开机启动 systemctl enable fail2ban
#查看被ban IP,其中sshd为名称,比如上面的
[wordpress] fail2ban-client status sshd
#删除被ban IP fail2ban-client set sshd delignoreip 192.168.111.111
#查看日志 tail /var/log/fail2ban.log

总结

fail2ban已经内置很多匹配规则,位于filter.d目录下,包含了常见的SSH/FTP/Nginx/Apache等日志匹配,
如果都还无法满足您的需求,您也可以自行新建规则来匹配异常IP。
使用fail2ban+Firewalld来阻止恶意IP是行之有效的办法,可极大提高服务器安全。

三、在自己 linux + docker + lamp + wordpress 环境上部署 fail2ban

1、时间总是过的很快
刚刚把别人的优秀文档借鉴过来,时间已经23:45,备份已经完成,只能先睡觉,后面再部署

C++ 项目中的extern “C”

1、 C++ 项目中的extern “C” {}

// 在用C++的项目源码中,经常会不可避免的会看到下面的代码:

#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif

它到底有什么用呢,你知道吗?而且这样的问题经常会出现在面试 or 笔试中。

下面我就从以下几个方面来介绍它

1、#ifdef _cplusplus/ #endif _cplusplus 及发散

2、extern “C”

2.1、extern关键字
2.2、”C”
2.3、小结extern “C”
3、C和C++互相调用
3.1、C++的编译和连接
3.2、C的编译和连接
3.3、C++中调用C的代码
3.4、C中调用C++的代码
4、C和C++混合调用特别之处函数指针

1、#ifdef _cplusplus/#endif _cplusplus及发散

在介绍 extern "C" 之前,我们来看下 #ifdef _cplusplus / #endif _cplusplus 的作用。
很明显 #ifdef / #endif 、#ifndef/#endif用于条件编译,#ifdef _cplusplus/#endif _cplusplus
—--—表示如果定义了宏 _cplusplus,就执行 #ifdef/#endif 之间的语句,否则就不执行。
在这里为什么需要 #ifdef _cplusplus/#endif _cplusplus呢?因为 C 语言中不支持 extern "C" 声明,
如果你明白 extern "C" 的作用就知道在 C 中也没有必要这样做,这就是条件编译的作用!
在 .c 文件中包含了 extern "C" 时会出现编译时错误。
既然说到了条件编译,我就介绍它的一个重要应用——避免重复包含头文件。
还记得腾讯笔试就考过这个题目,给出类似下面的代码(下面是我最近在研究的一个开源 web 服务器 : Mongoose 的头文件 mongoose.h 中的一段代码):

#ifndef MONGOOSE_HEADER_INCLUDED
#define    MONGOOSE_HEADER_INCLUDED
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
/*...................  * do something here  *................... */
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif /* MONGOOSE_HEADER_INCLUDED */
然后叫你说明上面宏#ifndef/#endif的作用?
为了解释一个问题,我们先来看两个事实:
这个头文件 mongoose.h 可能在项目中被多个源文件包含(#include "mongoose.h"),而对于一个大型项目来说,
这些冗余可能导致错误,因为一个头文件包含类定义或inline函数,在一个源文件中mongoose.h可能会被 #include 两次
(如,a.h 头文件包含了 mongoose.h,而在 b.c文件中 #include a.h和 mongoose.h)----这就会出错(在同一个源文件中一个结构体、类等被定义了两次)。
从逻辑观点和减少编译时间上,都要求去除这些冗余。然而让程序员去分析和去掉这些冗余,不仅枯燥且不太实际,最重要的是有时候又需要这种冗余来保证各个模块的独立。

为了解决这个问题,上面代码中的
#ifndef MONGOOSE_HEADER_INCLUDED
#define    MONGOOSE_HEADER_INCLUDED
/*……………………………*/
#endif /* MONGOOSE_HEADER_INCLUDED */
就起作用了。
如果定义了 MONGOOSE_HEADER_INCLUDED,#ifndef/#endif 之间的内容就被忽略掉。
因此,编译时第一次看到 mongoose.h 头文件,它的内容会被读取且给定 MONGOOSE_HEADER_INCLUDED 一个值。
之后再次看到 mongoose.h 头文件时,MONGOOSE_HEADER_INCLUDED 就已经定义了,mongoose.h 的内容就不会再次被读取了。

2、extern “C”

首先从字面上分析 extern "C",它由两部分组成----extern 关键字、"C"。下面我就从这两个方面来解读extern "C"的含义。

2.1、extern关键字

在一个项目中必须保证函数、变量、枚举等在所有的源文件中保持一致,除非你指定定义为局部的。
首先来一个例子:
//file1.c:     int x=1;     int f(){do something here}
//file2.c:     extern int x;     int f();     void g(){x=f();}
在 file2.c 中 g() 使用的 x 和 f() 是定义在 file1.c 中的。
extern 关键字表明 file2.c 中 x,仅仅是一个变量的声明,其并不是在定义变量 x,并未为x分配内存空间。
变量 x 在所有模块中作为一种全局变量只能被定义一次,否则会出现连接错误。
但是可以声明多次,且声明必须保证类型一致,如:
//file1.c:     int x=1;     int b=1;     extern c;
//file2.c:     int x;// x equals to default of int type 0     int f();     extern double b;     extern int c; 
在这段代码中存在着这样的三个错误:
x被定义了两次
b两次被声明为不同的类型
c被声明了两次,但却没有定义

回到 extern 关键字,extern 是 C/C++ 语言中表明函数和全局变量作用范围(可见性)的关键字,
该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用。
通常,在模块的头文件中对本模块提供给其它模块引用的函数和全局变量以关键字extern声明。
例如,模块 B 欲引用模块 A 中定义的全局变量和函数时只需包含模块A的头文件即可。这样,模块 B 中调用模块 A 中的函数时,在编译阶段,模块B虽然找不到该函数,但是并不会报错;
它会在连接阶段中从模块A编译生成的目标代码中找到此函数。
与 extern 对应的关键字是 static,被它修饰的全局变量和函数只能在本模块中使用。
因此,一个函数或变量只可能被本模块使用时,其不可能被extern “C”修饰。

2.2、”C”

典型的,一个 C++ 程序包含其它语言编写的部分代码。
类似的,C++ 编写的代码片段可能被使用在其它语言编写的代码中。
不同语言编写的代码互相调用是困难的,甚至是同一种编写的代码但不同的编译器编译的代码。
例如,不同语言和同种语言的不同实现可能会在注册变量保持参数和参数在栈上的布局,这个方面不一样。
为了使它们遵守统一规则,可以使用 extern 指定一个编译和连接规约。
例如,声明 C 和 C++ 标准库函数 strcpy(),并指定它应该根据C的编译和连接规约来链接:
extern "C" char* strcpy(char*,const char*);
注意它与下面的声明的不同之处:
extern char* strcpy(char*,const char*);

下面的这个声明仅表示在连接的时候调用strcpy()。
extern "C" 指令非常有用,因为 C 和 C++ 的近亲关系。
注意:
   extern "C" 指令中的 C,表示的一种编译和连接规约,而不是一种语言。

C 表示符合 C 语言的编译和连接规约的任何语言,如 Fortran、assembler 等。
还有要说明的是,extern "C" 指令仅指定编译和连接规约,但不影响语义。

例如在函数声明中,指定了 extern "C",仍然要遵守 C++ 的类型检测、参数转换规则。
再看下面的一个例子,为了声明一个变量而不是定义一个变量,你必须在声明时指定 extern 关键字,但是当你又加上了"C",它不会改变语义,但是会改变它的编译和连接方式。
如果你有很多语言要加上 extern "C",你可以将它们放到extern "C"{ }中。

2.3、小结extern “C”

通过上面两节的分析,我们知道 extern "C" 的真实目的是实现类 C 和 C++ 的混合编程。
在 C++ 源文件中的语句前面加上 extern "C",表明它按照类 C 的编译和连接规约来编译和连接,而不是C++ 的编译的连接规约。
这样在类 C 的代码中就可以调用 C++ 的函数 or 变量等。
(注:我在这里所说的类 C,代表的是跟 C 语言的编译和连接方式一致的所有语言)

3、C和C++互相调用

我们既然知道 extern "C" 是实现的类 C 和 C++ 的混合编程。
下面我们就分别介绍如何在 C++ 中调用 C 的代码、C 中调用 C++ 的代码。
首先要明白 C 和 C++ 互相调用,你得知道它们之间的编译和连接差异,及如何利用 extern "C" 来实现相互调用。

3.1、C++的编译和连接

C++ 是一个面向对象语言(虽不是纯粹的面向对象语言),它支持函数的重载,重载这个特性给我们带来了很大的便利。
为了支持函数重载的这个特性,C++编译器实际上将下面这些重载函数:

void print(int i);
void print(char c);
void print(float f);
void print(char* s);
编译为:
_print_int
_print_char
_print_float
_pirnt_string
这样的函数名,来唯一标识每个函数。
注:
    不同的编译器实现可能不一样,但是都是利用这种机制。
所以当连接是调用 print(3)时,它会去查找 _print_int(3) 这样的函数。
下面说个题外话,正是因为这点,重载被认为不是多态,多态是运行时动态绑定(“一种接口多种实现”),如果硬要认为重载是多态,它顶多是编译时“多态”。
C++中的变量,编译也类似,如全局变量可能编译g_xx,类变量编译为c_xx等。连接是也是按照这种机制去查找相应的变量。

3.2、C的编译和连接

C 语言中并没有重载和类这些特性,故并不像 C++ 那样 print(int i),会被编译为_print_int,而是直接编译为_print等。
因此如果直接在 C++ 中调用 C 的函数会失败,因为连接是调用 C 中的 print(3) 时,
它会去找 _print_int(3)。因此 extern "C"的作用就体现出来了。

3.3、C++中调用C的代码

假设一个 C 的头文件 cHeader.h 中包含一个函数 print(int i),为了在C++中能够调用它,
必须要加上 extern 关键字(原因在 extern 关键字那节已经介绍)。
它的代码如下:
#ifndef C_HEADER
#define C_HEADER
    extern void print(int i);
#endif
C_HEADER 相对应的实现文件为 cHeader.c 的代码为:
#include <stdio.h>
#include "cHeader.h"
void print(int i)
{
    printf("cHeader %d\n",i);
}

现在 C++ 的代码文件 C++.cpp 中引用 C 中的 print(int i) 函数:
extern "C"{
    #include "cHeader.h"
}
int main(int argc,char** argv)
{
    print(3);
    return 0;
}

3.4、C 中调用 C++ 的代码

现在换成在 C 中调用 C++ 的代码,这与在 C++ 中调用 C 的代码有所不同。
如下在 cppHeader.h 头文件中定义了下面的代码:

#ifndef CPP_HEADER
#define CPP_HEADER
    extern "C" void print(int i);
#endif //CPP_HEADER
相应的实现文件cppHeader.cpp文件中代码如下:
#include "cppHeader.h"
#include <iostream>
using namespace std;
void print(int i)
{
    cout<<"cppHeader "<<i<<endl;
}
在 C 的代码文件 c.c 中调用print函数:
extern void print(int i);
int main(int argc,char** argv)
{
    print(3);
    return 0;
}
注意在 C 的代码文件中直接 #include "cppHeader.h" 头文件,编译出错。
而且如果不加 extern int print(int i) 编译也会出错。

4、C和C++混合调用特别之处函数指针

当我们 C 和 C++ 混合编程时,有时候会用一种语言定义函数指针,而在应用中将函数指针指向另一种语言定义的函数。
如果 C 和 C++ 共享同一种编译和连接、函数调用机制,这样做是可以的。
然而,这样的通用机制,通常不会假定它存在,因此我们必须小心地确保函数以期望的方式调用。
而且当指定一个函数指针的编译和连接方式时,函数的所有类型,包括函数名、函数引入的变量也按照指定的方式编译和连接。如下例:

typedef int (*FT) (const void* ,const void*);//style of C++
extern "C" {
  typedef int (*CFT) (const void*,const void*);  //style of C
  void qsort(void* p,size_t n,size_t sz,CFT cmp);//style of C
}
void isort(void* p,size_t n,size_t sz,FT cmp);//style of C++
void xsort(void* p,size_t n,size_t sz,CFT cmp);//style of C
//style of C
extern "C" void ysort(void* p,size_t n,size_t sz,FT cmp);
int compare(const void*,const void*);//style of C++
extern "C" ccomp(const void*,const void*);//style of C
void f(char* v,int sz)
{
    //error,as qsort is style of C
    //but compare is style of C++
    qsort(v,sz,1,&compare);
    qsort(v,sz,1,&ccomp);//ok
    isort(v,sz,1,&compare);//ok
    //error,as isort is style of C++
    //but ccomp is style of C
    isort(v,sz,1,&ccopm);
}
注意:
    typedef int (*FT) (const void* ,const void*),表示定义了一个函数指针的别名FT,
这种函数指针指向的函数有这样的特征:
    返回值为int型、有两个参数,参数类型可以为任意类型的指针(因为为void*)。

最典型的函数指针的别名的例子是,信号处理函数signal,它的定义如下:
typedef void (*HANDLER)(int);
HANDLER signal(int ,HANDLER);
上面的代码定义了信函处理函数signal,它的返回值类型为HANDLER,有两个参数分别为int、HANDLER。
这样避免了要这样定义signal函数:
void (*signal (int ,void(*)(int) ))(int)
比较之后可以明显的体会到typedef的好处。

[工程中 C 文件调用 C++ 函数](https://www.cnblogs.com/cfzhang/p/25bbffa718da778a213c94ebd6971528.html)

1、描述

C调用C++链接库:
 1.编写C++代码,编写函数的时候,需要加入对C的接口,也就是extern “c"
 2.由于C不能直接用"class.function”的形式调用函数,所以C++中需要为C写一个接口函数。例如本来要调用student类的talk函数,就另外写一个cfun(),专门建一个student类,并调用talk函数。而cfun()要有extern声明
 3.我在练习中就使用在C++头文件中加extern ”c”的方法。而C文件要只需要加入对cpp.h的引用
 4.详细见如下代码:
  student是一个类,里边有talk函数,就输出一句话而已
  cpp.cpp与cpp.h是两个C++代码,包含对C的接口
  最后用C代码:helloC.c来测试结果

代码

1、student.cpp

#include <iostream>
using namespace std;
#include "student.h"
void student::talk()
{
    cout<<"I am Kenko"<<endl;
}

### 2、student.h
```c
#ifndef _STUDENT_
#define _STUDENT_
class student {
    public:
        void talk();
};
#endif

3、cpp.h

#ifndef _CPP_
#define _CPP_
    #include "student.h"

#ifdef __cplusplus
    extern "C" {
#endif
    void helloCplusplus();
#ifdef __cplusplus
}
#endif
#endif

4、cpp.cpp

#include <iostream>
using namespace std;
#include "cpp.h"
student stu;
void helloCplusplus() {
    cout << "hello C++" << endl;
    stu.talk();
}

void helloCplusplus2() {
    cout << "hello C++" << endl;
}

5、helloC.c

#include <stdio.h>
#include "cpp.h"
int main() {
    helloCplusplus();
    return 0;
}

3、怎样编译运行

linux 编译:
  g++ -fPIC -shared -g -o libcccall.so cpp.cpp student.cpp
  g++ -g helloC.c ./libccall.so -o main

cmder

一、用自己习惯的方式启动

1、研究它保存配置文件存放位置

对 cmder 的风格,不同风格操作上有所区别。
界面看上去效果也不同,最好能根据需要自由启动。

启动方式,通过 cmder 的settings-->> startup 最下方,
在cmder:cmder时,它给出的提为:
cmd /c cmder\vender\init.bat

当然 bash:bash / minitty 等不同风格,启动命令也各不相同

保存风格后,发现cmder\vendor\conemu-maximus5\ConEmu.xml 修改时间发生改变,OK,那就是它了。

2、简单修改,方便启动

A、修改配置文件

然后将相应的配置分别重名为:
ConEmu_minitty.xml
ConEmu_minitty_admin.xml
ConEmu_cmder_cmder.xml
ConEmu_cmder_cmder_admin.xml
ConEmu_bash_bash.xml
ConEmu_bash_bash_admin.xml

B、建立几个启动文件

这里仅仅列出一个为例:
bash_cmder_admin.bat 内容如下:
copy cmderPath\vender\conemu-maximus5\ConEmu_cmder_cmder_admin.xml cmderPath\vender\conemu-maximus5\ConEmu.xml

cmd /c cmderPath\cmder.exe

二、别人提供的修改方法

https://www.jianshu.com/p/979db1a96f6d

三、同类工具

mobaxterm
guake
secureCRT
putty/mtputty

bat

一、dos下计算两个%time%时间差

1、场景一(此脚本基本调试通过)

bat语句中计算两个时间差,可以先将时间转换成秒数,然后,将两个时间数进行相减即可,参考代码(此代码中问题多多):
@echo off
set ns=0
rem 显示开始时间
set time1=%time%
echo 当前时间是%time1%
call :time2sec %time1%
set t1=%ns%
pause

rem 显示结束时间
set time2=%time%
echo 当前时间是%time2%
call :time2sec %time2%
set t2=%ns%
rem 计算时间差,计算中,最好把表达式用引号括起来
set /a "tdiff=(%t2%-%t1%)"
echo diff %time1% from %time2% is %tdiff% seconds.
pause
goto :eof

:time2sec
rem 将时间转换成秒数,保存到ns中
rem %t1 是函数的第一个入参
set tt=%1
rem 从第一(0)个字符开始,取两个长度
rem 这个用法其实有问题,当小时不是两位数的时候,取值发生错位
set hh=%tt:~0,2%
set mm=%tt:~3,2%
set ss=%tt:~6,2%
rem 表达式需要用引号,否则问题报错,
rem 说括号不配对,花了很长时间才解决
set /a "ns=(%hh%*60+%mm%)*60+%ss%"
goto :eof

2、场景二(脚本尚未调试)

@echo off
title 同一月份下的耗时计算
::获取起始月份、起始日期、起始小时和起始分钟
set startmonth=%date:~5,2%
set startday=%date:~8,2%
set starthour=%time:~0,2%
set startmin=%time:~3,2%

echo.&echo 修改系统日期和时间为未来同一个月份下的某日某月某时某分 以便测试脚本
echo 完成修改后 按任意键继续
pause >nul
::获取终止月份、终止日期、终止小时和终止分钟
set endmonth=%date:~5,2%
set endday=%date:~8,2%
set endhour=%time:~0,2%
set endmin=%time:~3,2%  
::初始化间隔日期变量、间隔小时变量和间隔分钟变量
set intday=0
set inthour=0
set intmin=0
::初始化耗时变量
set inttime=0 
::如果结束月份和起始月份不在同一月 则调用calc4标签
if %endmonth% NEQ %startmonth% (call:calc4 & goto :finalresult)
::如果结束日期等于起始日期 则调用calc1标签
if %endday% EQU %startday% (call:calc1 & goto :finalresult)
::如果结束日期大于起始日期 则调用calc2标签
if %endday% GTR %startday% (call:calc2 & goto :finalresult)
::如果结束日期小于起始日期 则调用calc3标签
if %endday% LSS %startday% (call:calc3 & goto :finalresult)
::备注:没有规避同一天内结束小时小于起始小时、以及同一小时内结束分钟小于起始分钟的情况,因为在脚本运行过程中,逻辑上一般不会出现这两种情况。
::显示耗时
:finalresult
echo 耗时:%inttime%
exit /b
::同一天内的耗时计算 需考虑到结束分钟小于起始分钟的时候 从终止小时借位的情况
:calc1
if /i %endmin% LSS %startmin% (set /a intmin=endmin+60-startmin & set /a endhour-=1) else (set /a intmin=endmin-startmin)
set /a inthour=endhour-starthour
set /a intday=endday-startday
set inttime=%intday%天%inthour%小时%intmin%分钟
goto :eof
::同一月份但不同天内的耗时计算 需考虑到结束分钟小于起始分钟的时候 从终止小时借位的情况 需考虑到结束小时小于起始小时的时候 从终止日期借位的情况
:calc2
if /i %endmin% LSS %startmin% (set /a intmin=endmin+60-startmin & set /a endhour-=1) else (set /a intmin=endmin-startmin)
if /i %endhour% LSS %starthour% (set /a inthour=endhour+24-starthour & set /a endday-=1) else (set /a inthour=endhour-starthour)
set /a intday=endday-startday
set inttime=%intday%天%inthour%小时%intmin%分钟
goto :eof
::同一月份下 结束日期逻辑上不能小于起始日期 抛出错误
:calc3
set inttime=错误!结束日期小于起始日期!
goto :eof
::跨月份的情况忽略不计
:calc4
set inttime=跨月份忽略耗时计算
goto :eof

二、延时指定时间,任意时长都可以设置

@echo of
echo this window will disappear 3 seconds later
‘自动生成一个VB脚本,参数0表示第一个参数
'应该是特殊字符的转意字符
‘1000毫秒是一秒
echo if wscript.argument(0)^>0 then \
    wscript.Sleep(wscript.argument(0)*1000):end if>"%Temp%\delay01.vbs"
'执行脚本
cscript "%Temp\delay01.vbs" 3
‘执行结束后,删除脚本
del "%Temp\delay01.vbs"

三、找到并多杀死进程

1、找到进程

tasklist | findstr /i frps.exe

2、杀死进程

taskkill /f /im frps.exe

四、win7 快速进入开机设置目录

win+R 然后输入: shell:startup

五、想在DOS命令行下,查看内存情况

c:> systeminfo
然后,会看到各种详细信息

c:> systeminfo | findstr 内存

centos

一、RPM 资源

1、网络资源

http://rpmfind.net/linux/rpm2html/

2、下载到本地后,安装方法

rpm -ivh ab.rpm

3、一位老兄详细的文档,还讲解了怎样将windows目录挂载到linux

https://blog.csdn.net/annicybc/article/details/1133899
https://blog.csdn.net/weixin_40973138/article/details/103724390