tigerVNC

一、来源

http://m.mamicode.com/info-detail-1661709.html
我曾经按照它的提示,操作成功

二、内容如下,操作成功

CentOS7下VNC服务器-tigervnc配置
将我的实际操作整理成文档供大家参考,也供自己以后查阅。正文如下:

1、如果第一次安装tigervnc,可以跳过第2步;

2、如果以前安装过

    需要通过以下几步删除tigervnc:
   卸载原安装的服务器软件:yum remove tigervnc-server
   删除/etc/systemd/system文件夹下的配置文件:vncserver@:1.service等
   删除可远程VNC连接的账户home目录下的.vnc文件夹:remove -rf ~\.vnc
   查看/tmp/.X11-unix目录,将X[1-9]的文件删掉,注意X0不能删除;
   最后重启OS

3、安装tigervnc:yum install tigervnc-server

--------------------------以上为root用户操作事项-------------------------
--------------------------以下分别以root账户和其它账户进行配置------------------
【root账户:操作均在root账户下执行】

4、在/etc/systemd/system文件下创建配置文件

cp /lib/systemd/system/vncserver@.service /etc/systemd/system/vncserver@:1.service
   这里需要注意的是上面这个1代表服务端口号,可取其它值,此处的1代表开放的服务5901端口,以此类推

5、编辑配置文件

vim /etc/systemd/system/vncserver@:1.service,更改如下两行(红色行),将<USER>改为root, 将PIDFile=后面的/home去掉
[...]
[Service]
Type=forking
# Clean any existing files in /tmp/.X11-unix environment
ExecStartPre=/bin/sh -c ‘/usr/bin/vncserver -kill %i > /dev/null 2>&1 || :‘
# ExecStart=/sbin/runuser -l <USER> -c "/usr/bin/vncserver %i"  
# PIDFile=/home/<USER>/.vnc/%H%i.pid
ExecStart=/sbin/runuser -l root -c "/usr/bin/vncserver %i"
PIDFile=/root/.vnc/%H%i.pid
ExecStop=/bin/sh -c ‘/usr/bin/vncserver -kill %i > /dev/null 2>&1 || :‘

6、设置vnc远程连接密码:vncpasswd

7、添加防火墙规则

firewall-cmd --permanent --zone=public --add-service vnc-server
firewall-cmd --reload
如果提示firewalld没启动,请运行firewalld启动防火墙,再运行上面两条命令;

8、设置启动

systemctl daemon-reload 重启daemon使上述配置生效
systemctl enable vncserver@:1.service 使服务自动启动
systemctl start vncserver@:1.service 启动服务
【其它账户:以账户名为cs1为例】

回到4、在/etc/systemd/system文件下创建配置文件[root账号下操作]

cp /lib/systemd/system/vncserver@.service /etc/systemd/system/vncserver@:2.service
这里需要注意的是上面这个2代表服务端口号,可取其它值,此处的2代表开放的服务5902端口,以此类推

重复5~8、编辑配置文件[root账号下操作]

“`vim /etc/systemd/system/vncserver@:1.service,更改如下两行(红色行),将改为cs1
[…]
[Service]
Type=forking

<h1>Clean any existing files in /tmp/.X11-unix environment</h1>

ExecStartPre=/bin/sh -c ‘/usr/bin/vncserver -kill %i > /dev/null 2>&1 || :‘

<h1>ExecStart=/sbin/runuser -l <USER> -c "/usr/bin/vncserver %i"</h1>

<h1>PIDFile=/home/<USER>/.vnc/%H%i.pid</h1>

ExecStart=/sbin/runuser -l cs1 -c "/usr/bin/vncserver %i"
PIDFile=/home/cs1/.vnc/%H%i.pid
ExecStop=/bin/sh -c ‘/usr/bin/vncserver -kill %i > /dev/null 2>&1 || :‘
6、设置vnc远程连接密码[cs1账号下操作]:vncpasswd
7、添加防火墙规则<a href="如果以前已添加,此步骤可省略">root账号下操作</a> firewall-cmd –permanent –zone=public –add-service vnc-server firewall-cmd –reload 如果提示firewalld没启动,请运行firewalld启动防火墙,再运行上面两条命令;
8、设置启动[root账号下操作]
systemctl daemon-reload 重启daemon使上述配置生效
systemctl enable vncserver@:2.service 使服务自动启动
systemctl start vncserver@:2.service 启动服务

参考:http://www.centoscn.com/image-text/install/2016/0407/7008.html http://www.cnblogs.com/h2zZhou/p/5209948.html
本文出自 “韩伟” 博客,请务必保留此出处http://davidhan.blog.51cto.com/511334/1886761
“`

tee

tee用途说明

把linux命令输出重定向到文件中:
ls >a.txt,
但不能看到输出,如果我们既想把输出保存到文件中,
又想在屏幕上看到输出内容,就可以使用tee命令了。
tee命令读取标准输入,把这些内容同时输出到标准输出和(多个)文件中,
tee命令可以重定向标准输出到多个文件。
要注意的是:
   在使用管道线时,前一个命令的标准错误输出不会被tee读取。

常用参数

1、语法

1、只输出到标准输出,格式:tee

2、输出到标准输出的同时,保存到文件file中。

如果文件不存在,则创建;如果已经存在,则覆盖之。
格式:tee file

3、输出到标准输出的同时,追加到文件file中。如果文件不存在,则创建;如果已经存在,就在末尾追加内容,而不是覆盖。

格式:tee -a file

4、多次输出到标准设备,如

两次: tee - ,三次: tee -- 四次:tee ---

5、输出到标准设备多次,同时保存到file1/file2中。

格式:tee file1 file2 -
     tee file1 file2 --

2、示例

示例一 tee命令与重定向的对比

[root@web ~]# seq 5 >1.txt 
[root@web ~]# cat 1.txt 
1
2
3
4
5
[root@web ~]# cat 1.txt >2.txt 
[root@web ~]# cat 1.txt | tee 3.txt 
1
2
3
4
5
[root@web ~]# cat 2.txt
1
2
3
4
5
[root@web ~]# cat 3.txt
1
2
3
4
5
[root@web ~]# cat 1.txt >>2.txt
[root@web ~]# cat 1.txt | tee -a 3.txt
1
2
3
4
5
[root@web ~]# cat 2.txt
1
2
3
4
5
1
2
3
4
5
[root@web ~]# cat 3.txt
1
2
3
4
5
1
2
3
4
5
[root@web ~]#

示例二 使用tee命令重复输出字符串

[root@web ~]# echo 12345 | tee
12345
[root@web ~]# echo 12345 | tee -
12345
12345
[root@web ~]# echo 12345 | tee - -
12345
12345
12345
[root@web ~]# echo 12345 | tee - - -
12345
12345
12345
12345
[root@web ~]# echo 12345 | tee - - - -
12345
12345
12345
12345
12345
[root@web ~]#
[root@web ~]# echo -n 12345 | tee
12345[root@web ~]# echo -n 12345 | tee -
1234512345[root@web ~]# echo -n 12345 | tee - -
123451234512345[root@web ~]# echo -n 12345 | tee - - -
12345123451234512345[root@web ~]# echo -n 12345 | tee - - - -
1234512345123451234512345[root@web ~]#
示例三 使用tee命令把标准错误输出也保存到文件
[root@web ~]# ls "*"
ls: *: 没有那个文件或目录
[root@web ~]# ls "*" | tee -
ls: *: 没有那个文件或目录
[root@web ~]# ls "*" | tee ls.txt
ls: *: 没有那个文件或目录
[root@web ~]# cat ls.txt
[root@web ~]# ls "*" 2>&1 | tee ls.txt
ls: *: 没有那个文件或目录
[root@web ~]# cat ls.txt
ls: *: 没有那个文件或目录
[root@web ~]#

tcl

一、与tcl结缘

它时最老牌的自动化脚本语言。初学时痛苦万分,遇到问题时无处寻求帮助。曾经下决心将其抛弃。
但就如同对键盘的复杂情感,用它做了不少工作。后来也真的放弃了。
忽然有一些发现某些事情,只需写几行简单的自动化代码即可完成,于是又想起了tcl~~~真香。
中途也用过 python--paramiko,写起来确实比较轻松,但是本质上套路还是一样。

二、tcl安装

centos直接安装:
    yum install -y tcl tclx tcl-devel
源码包安装:
    ./configure --with-tcl=/usr/lib --with-tclinclude=/usr/include/tcl-private/generic
    make && make install  安装完毕

还有 expect 也需要安装

三、tcl 基础语法

1、变量的定义与赋值

set a 0.0000016 ==>>0.0000016
set b [expr 1.0*$a] ==>> 0.0000016 ===>>1.6e-06
puts [format "%10.8f" $b] ==>> 0.00000160
puts [format "%10.6f" $b] ==>> 0.000002  打印的小数只有六位,最后被四舍五入

2、打印文件名及其大小

format "\"%s\" size=%d" $fileName [file size $fileName]

3、expr 关键字,数值计算

set a 101
set b 201
set sum1 [expr $a+$b]
set sum2 [expr $sum1+100]

set 1 100  ==>>> 这是什么神奇语法,实践下来果然如此
% set 1 200
200
% puts [expr 1 + 2]
3
% puts [expr $1 + 2]
202
% set 1 33
33
% puts [expr $1+2]
35

4、proc 程序

proc sum {arg1 arg2} {
   set r [expr $arg1+$arg2]
   return $r
}

sum 1 2

5、条件判断 if 关键字

if bool then body1 else body2

set r [expr $b%10]
set q [expr $b/10]
if {$r >= 5} then {
    set b [expr ($q+1)*10]
} else {
    set b [expr $q*10]
}

6、循环控制

A、while 循环
语法:while bool body

# 计算1000以内所有偶数之和
set a 0
set sum 0
while {$a <= 1000} {
   set sum [expr $sum+$a]
   set a [expr $a + 2]
}

B、for 循环
语法:for init bool step body

# 计算1000以内所有偶数之和
for {set a 0; set sum 0} {$a <= 1000} {set a [expr $a + 2]} {
   set sum [expr $sum + $a]
}

C、foreach 循环
语法:foreach var valueList body 

# 计算1000以内所有偶数之和
set L [list]
for {set a 0} {$a <= 1000} {incr a 2} {
   lappend L $a
}

set sum 0
foreach b $L {
   set sum [expr $sum + $b]
}

7、列表与数组

A、列表

举例,一个包含七个元素的列表: {1 2 a {3 {4 5} 6} "bcd ef" 7 {g h}}
其中第四个又是一个列表,第五个是一个字符串

简单的登陆别的机器执行命令,可行 (更详细的还得参考expect的有关说明)
expect.sh

#!/usr/local/bin/expect
set timeout 10
spawn ssh root@ip
expect "*password*"
send "123456\r"
expect "#"
send "service crond restart\r"
expect eof

注:
expect的功能是很强大的,实现了无须人与系统交互的功能,
已经成为SA的一个强大助手,要研究的彻底,还是需要大量的时间的。

一、tcl 概述

我们通过 Shell 可以实现简单的控制流功能,如:循环、判断等。
但是对于需要交互的场合则必须通过人工来干预,有时候我们可能会需要实现和交互程序如 telnet 服务器等进行交互的功能。
而Expect就使用来实现这种功能的工具。
  Expect是一个免费的编程工具语言,用来实现自动和交互式任务进行通信,而无需人的干预。
Expect 的作者 Don Libes 在 1990 年开始编写 Expect 时对 Expect 做有如下定义 :
   Expect 是一个用来实现自动交互功能的软件套件 \(Expect [is a] software suite for automating interactive tools)。
   使用它系统管理员可以创建脚本用来实现对命令或程序提供输入,而这些命令和程序是期望从终端(terminal)得到输入,一般来说这些输入都需要手工输入进行的。
   Expect则可以根据程序的提示模拟标准输入提供给程序需要的输入来实现交互程序执行。甚至可以实现实现简单的 BBS 聊天机器人。
  Expect是不断发展的,随着时间的流逝,其功能越来越强大,已经成为系统管理员的的一个强大助手。Expect需要Tcl编程语言的支持,要在系统上运行Expect必须首先安装Tcl。

二、Expect工作原理

从最简单的层次来说,Expect的工作方式象一个通用化的 Chat 脚本工具。
Chat 脚本最早用于 UUCP 网络内,以用来实现计算机之间需要建立连接时进行特定的登录会话的自动化。
Chat 脚本由一系列 expect-send 对组成 :
   expect 等待输出中输出特定的字符,通常是一个提示符,然后发送特定的响应。

例如下面的 Chat脚本实现等待标准输出出现 Login:字符串,然后发送somebody作为用户名;然后等待Password:提示符,并发出响应 sillyme。
  引用:Login: somebody Password: sillyme
  这个脚本用来实现一个登录过程,并用特定的用户名和密码实现登录。
  Expect最简单的脚本操作模式本质上和 Chat 脚本工作模式是一样的。

例子:

1、实现功能

下面我们分析一个响应 chsh 命令的脚本。
我们首先回顾一下这个交互命令的格式。假设我们要为用户chavez改变登录脚本,要求实现的命令交互过程如下:
  引用:# chsh chavez
  Changing the login shell for chavez
  Enter the new value, or press return for the default
  Login Shell [/bin/bash]: /bin/tcsh
  #
可以看到该命令首先输出若干行提示信息并且提示输入用户新的登录 shell。
我们必须在提示信息后面输入用户的登录shell或者直接回车不修改登录shell。

2、下面是一个能用来实现自动执行该命令的 Expect 脚本

#!/usr/bin/expect
# Change a login shell to tcsh
set user [lindex $argv 0]
spawn chsh $user
expect "]:"
send "/bin/tcsh "
expect eof
exit

这个简单的脚本可以解释很多 Expect 程序的特性。
和其他脚本一样首行指定用来执行该脚本的命令程序,这里是/usr/bin/expect。
程序第一行用来获得脚本的执行参数(其保存在数组$argv中,从0号开始是参数),并将其保存到变量user中。
  第二个参数使用 Expect 的 spawn 命令来启动脚本和命令的会话,这里启动的是 chsh 命令,实际上命令是以衍生子进程的方式来运行的。
  随后的 expect 和 send 命令用来实现交互过程。脚本首先等待输出中出现 ]: 字符串,一旦在输出中出现chsh输出到的特征字符串(一般特征 字符串往往是等待输入的最后的提示符的特征信息)。
  对于其他不匹配的信息则会完全忽略。当脚本得到特征字符串时,expect将发送/bin/tcsh和 一个回车符给chsh命令。
  最后脚本等待命令退出(chsh结束),一旦接收到标识子进程已经结束的eof字符,expect脚本也就退出结束。

3、决定如何响应

管理员往往有这样的需求,希望根据当前的具体情况来以不同的方式对一个命令进行响应。
我们可以通过后面的例子看到 expect 可以实现非常复杂的条件响应,而仅仅通过简单的修改预处理脚本就可以实现。
下面的例子是一个更复杂的expect-send例子:
  expect -re "\[(.*)]:"
  if {$expect_out(1,string)!="/bin/tcsh"} {
      send "/bin/tcsh"
    }
  send " "
  expect eof

在这个例子中,第一个 expect 命令现在使用了 -re 参数,这个参数表示指定的的字符串是一个正则表达式,而不是一个普通的字符串。
对于上面这个例子里是查找一个左方括号字符(其必须进行三次逃逸(escape),因此有三个符号,
因为它对于 expect 和正则表达时来说都是特殊字符)后面跟有零个或多个字符,最后是一个右方括号字符。
这里.*表示表示一个或多个任意字符,将其存放在()中是因为将匹配结果存放在一个变量中以实现随后的对匹配结果的访问。

  当发现一个匹配则检查包含在[]中的字符串,查看是否为/bin/tcsh。
如果不是则发送 /bin/tcsh 给 chsh 命令作为输入,如果是则仅仅发送一个回车符。
这个简单的针对具体情况发出不同相响应的小例子说明了expect的强大功能。

  在一个正则表达时中,可以在()中包含若干个部分并通过 expect_out 数组访问它们。
各个部分在表达式中从左到右进行编码,从 1 开始(0 包含有整个匹配输出)。
() 可能会出现嵌套情况,这种情况下编码从最内层到最外层来进行的。

4、使用超时

下一个 expect 例子中将阐述具有超时功能的提示符函数。
这个脚本提示用户输入,如果在给定的时间内没有输入,则会超时并返回一个默认的响应。
这个脚本接收三个参数:提示符字串,默认响应和超时时间(秒)。
  #!/usr/bin/expect
  # Prompt function with timeout and default.
  set prompt [lindex $argv 0]
  set def [lindex $argv 1]
  set response $def
  set tout [lindex $argv 2]

  脚本的第一部分首先是得到运行参数并将其保存到内部变量中。
  send_tty "$prompt: "
  set timeout $tout
  expect " " {
      set raw $expect_out(buffer)
      # remove final carriage return
      set response [string trimright "$raw" " "]
  }
  if {"$response" == ""} {set response $def}
  send "$response

这是脚本其余的内容。可以看到 send_tty 命令用来实现在终端上显示提示符字串和一个冒号及空格。
set timeout 命令设置后面所有的 expect 命令的等待响应的超时时间为 $tout (-l 参数用来关闭任何超时设置)。

然后 expect 命令就等待输出中出现回车字符。
如果在超时之前得到回车符,那么 set 命令就会将用户输入的内容赋值给变量 raw。
随后的命令将用户输入内容(最后的回车符号去除以后)赋值给变量 response。

然后,如果 response 中内容为空则将 response 值置为默认值(如果用户在超时以后没有输入或者用户仅仅输入了回车符)。
最后 send 命令将 response 变量的值加上回车符发送给标准输出。

一个有趣的事情是该脚本没有使用spawn命令。该expect脚本会与任何调用该脚本的进程交互。
如果该脚本名为 prompt,那么它可以用在任何 C 风格的 shell 中。
  % set a='prompt "Enter an answer" silence 10'
  Enter an answer: test
  % echo Answer was "$a"
  Answer was test
  prompt设定的超时为10秒。如果超时或者用户仅仅输入了回车符号,echo命令将输出
  Answer was "silence"

5、一个更复杂的例子

下面我们将讨论一个更加复杂的expect脚本例子,这个脚本使用了一些更复杂的控制结构和很多复杂的交互过程。
这个例子用来实现发送 write 命令给任意的用户,发送的消息来自于一个文件或者来自于键盘输入。
  #!/usr/bin/expect
  # Write to multiple users from a prepared file
  # or a message input interactively
  if {$argc<2} {
      send_user "usage: $argv0 file user1 user2 ... "
      exit
  }

  # send_user 命令用来显示使用帮助信息到父进程(一般为用户的shell)的标准输出。
  set nofile 0
  # get filename via the Tcl lindex function
  set file [lindex $argv 0]
  if {$file=="i"} {
      set nofile 1  # means not check file
  } else {
      # make sure message file exists
      if {[file isfile $file]!=1} {
          send_user "$argv0: file $file not found. "
          exit
        }
   }
  这部分实现处理脚本启动参数,其必须是一个储存要发送的消息的文件名或表示使用交互输入得到发送消的内容的"i"命令。

  变量 file 被设置为脚本的第一个参数的值,是通过一个 Tcl 函数 lindex 来实现的,
该函数从列表/数组得到一个特定的元素。[] 用来实现将函数 lindex 的返回值作为 set 命令的参数。

  如果脚本的第一个参数是小写的 "i",那么变量nofile被设置为1,否则通过调用 Tcl 的函数 isfile 来验证参数指定的文件存在,如果不存在就报错退出。

  可以看到这里使用了 if 命令来实现逻辑判断功能。
该命令后面直接跟判断条件,并且执行在判断条件后的 {} 内的命令。if 条件为 false 时则运行 else 后的程序块。
  set procs {}
  # start write processes
  for {set i 1} {$i<$argc} {incr i} {
      spawn -noecho write [lindex $argv $i]
      lappend procs $spawn_id
  }

最后一部分使用 spawn 命令来启动 write 进程实现向用户发送消息。
这里使用了 for 命令来实现循环控制功能,循环变量首先设置为 1,然后依次递增。循环体是最后的 {} 的内容。
这里我们是用脚本的第二个和随后的参数来 spawn 一个 write 命令,并将每个参数作为发送消息的用户名。
lappend 命令使用:保存每个 spawn 的进程的进程 ID 号的内部变量 $spawn_id 在变量 procs 中构造了一个进程ID号列表。

  if {$nofile==0} {
       setmesg [open "$file" "r"]
  } else {
      send_user "enter message,
      ending with ^D: "
    }

  最后脚本根据变量 nofile 的值实现打开消息文件或者提示用户输入要发送的消息。
  set timeout -1
  while 1 {
      if {$nofile==0} {
          if {[gets $mesg chars] == -1} {
                break
            }
          set line "$chars "
      } else {
          expect_user {
              -re " " {}
              eof break
            }
          set line $expect_out(buffer)
       }
       foreach spawn_id $procs {
          send $line
       }
       sleep 1
    }
    exit

上面这段代码说明了实际的消息文本是如何通过无限循环 while 被发送的。
while 循环中的 if 判断消息是如何得到的。
在非交互模式下,下一行内容从消息文件中读出,当文件内容结束时 while 循环也就结束了。(break命令实现终止循环) 。

  在交互模式下,expect_user 命令从用户接收消息,当用户输入ctrl+D 时结束输入,循环同时结束。
两种情况下变量 $line 都被用来保存下一行消息内容。当是消息文件时,回车会被附加到消息的尾部。

  foreach 循环遍历 spawn 的所有进程,这些进程的 ID 号都保存在列表变量 $procs 中,实现分别和各个进程通信。
send 命令组成了 foreach 的循环体,发送一行消息到当前的 write 进程。
while 循环的最后是一个 sleep 命令,主要是用于处理非交互模式情况下,以确保消息不会太快的发送给各个write进程。
当while循环退出时,expect脚本结束

tcl 之坑
1、tcl 脚本执行时,貌似没有按顺序执行
tcl 执行时,出现顺序混乱,纠结了很久

exp_send "\r\n"
expect {
    "root\ @\ user" { exp_send "ls\r"}
    timeout         { exit  0        }
}
expect {
    "root\ @\ user" { exp_send "make\r"}
    timeout         { exit  0        }
}
expect {
    "root\ @\ user" {
      puts "build finished!\r"
      exp_send "ls\r"
  }
    timeout         { exit  0        }
}

---->> 这段代码看似没问题,但是编译时,还没有编译结束,就先打印 "build finished".
究其原因,是因为先遇到了关键字 "root\ @\ user",所以进入下一段。
如果想要按照顺序执行,必须严格缩写 expect 字符串:
expect {
    # 这里是完整的命令行,不会再出现误判
    "\[root @ user ]$ " {
        puts "build finished!\r"
    }
  timeout { exit 0 }
}

tar

一、采用脚本自动压缩和解压

本文来源:https://www.cnblogs.com/xiongzaiqiren/p/12658210.html

 bat脚本(批处理程序)调用WinRAR及7zip压缩和解压缩
最近项目要用到定期批量将文件及文件夹下的文档打包,所以想到要写脚本来实现,然后做Windows定期任务调用。bat脚本(批处理程序)调用WinRAR及7zip压缩和解压缩。

1、调用WinRAR来实现

A、bat调用WinRAR解压实例一

因为WinRAR是Windows系统经典的压缩及解压缩程序,也是目前为止使用非常普遍。
它不是Windows系统自带的,所以要调用它之前得确认是否安装了WinRAR,
如果没有安装则自行下载安装即可。WinRAR下载地址:http://www.winrar.com.cn/
  一般来说,64位安装在C:\Program Files\目录下,32位安装
  在C:\Program Files (x86)\目录下。这个知识点很关键,下面要用到.
bat调用WinRAR解压实例一:
::解压程序WinRAR.exe所在目录 32位%ProgramFiles(x86)% 或64位 %ProgramFiles%
set WinRarDir=%ProgramFiles%\WinRAR 
:: x即解压。-y是说如果遇到提示说是否覆盖,选择yes
start  /wait  ""  "%WinRarDir%\WinRAR.exe"  x  -l  D:\Software\Redis-x64-3.2.100.zip  C:\mySoftWare\

B、bat调用WinRAR解压实例二

上面实例是调用解压的核心命令,使用到项目中还需要结合上下文完善,
比如判断winrar程序是否存在,目标压缩包zip是否存在等。

@echo off
setlocal enabledelayedexpansion
echo please make sure you are admin!
echo 解压并部署redis,notepad及相关软件,请确保相关压缩包在当前目录。
echo 执行此脚本前,请确认已经安装解压缩程序WinRAR.exe
:::::::::: 配置参数 ::::::::::
::解压程序WinRAR.exe所在目录 32位%ProgramFiles(x86)% 或64位 %ProgramFiles%
set WinRarDir=%ProgramFiles%\WinRAR 
REM 基础文件
set redis=Redis-x64-3.2.100.zip
set notepad=npp.7.5.6.Installer.exe
REM 部署盘符
set Pan=C:\
:::::::::: 执行脚本前,请配置以上参数。::::::::::
echo ------------------------------
set curdir=%~dp0
cd /d  %curdir%
echo 开始准备部署资源:
::检查基础文件
if exist %curdir%%redis% (echo 存在:%redis%) else (echo %redis%文件不存在 & pause&exit)
if exist %curdir%%notepad% (echo 存在:%notepad%) else (echo %notepad%文件不存在 & pause&exit) 
echo ------------------------------
::创建部署目录
set deploydir=%Pan%mySoftWare
if exist %Pan% ( ^ if exist %deploydir% (echo 存在:%deploydir%) else (mkdir %deploydir% & echo 已创建:%deploydir%)
) else ( ^echo !! echo !!执行失败,当前系统不存在%Pan%盘 echo !! pause & exit)
echo ------------------------------

::解压并放到部署目录 32位%ProgramFiles(x86)% 或64位 %ProgramFiles%
if exist "%WinRarDir%\WinRAR.exe" (echo 已找到:"%WinRarDir%\WinRAR.exe") else (echo 找不到:"%WinRarDir%\WinRAR.exe"请检查。 & pause&exit)
echo 开始解压部署资源:
::替换掉.zip即移除后缀名形成解压到文件夹名
set "redisdir=%redis:.zip=%"
:: x即解压。-y是说如果遇到提示说是否覆盖,选择yes
start /wait "" "%WinRarDir%\WinRAR.exe" x -l %curdir%%redis% %deploydir%\%redisdir%\
copy "%curdir%script\Redis*" "%deploydir%\%redisdir%\" 
echo 部署资源已准备好,开始安装辅助软件:
::运行npp.7.5.6.Installer.exe(安装notepad++)
start /wait "" "%curdir%%notepad%" /S
echo 已安装%notepad% 
echo 部署资源已准备好,你可以开始部署了(请注意修改redis,preview等相关应用配置文件)。
echo ------------------------------
echo 检查服务是否存在:
::检查服务是否存在
set redisServerName=Redis
sc query "%redisServerName%" >nul && (echo 服务已存在:%redisServerName%) || (echo 请安装部署:%redisServerName%)
echo 检查服务已完成。 
echo ------------------------------
echo 部署资源已准备好,请开始部署吧。
::部署完成,打开windows服务管理器
start "" "services.msc" -refresh
pause

2、调用 7zip 来实现

A、bat调用7zip压缩实例

7zip也是目前为止使用非常普遍的一种压缩程序,它的压缩、解压缩效率高,
而且没有广告,是免费工具。它不是Windows系统自带的,所以要调用它之前得确认是否安装了7zip,
如果没有安装则自行下载安装即可。7zip下载地址:https://sparanoid.com/lab/7z/

@echo 定位到待压缩的文件夹
cd F:\数据压缩测试\0101C  
@echo 使用7z先将1这个目录下的所有文件压缩为tar格式 
"C:\Program Files\7-Zip\7z.exe" a -ttar F:\数据压缩测试\510101C.tar 1\*
@echo 将压缩后的tar格式再压缩为gzip格式,最终得到需要的tar.gz格式
"C:\Program Files\7-Zip\7z.exe" a -tgzip F:\数据压缩测试\2016_1_510101C.tar.gz F:\数据压缩测试\510101C.tar
@删除中间过程生成的文件
del "F:\数据压缩测试\510101C.tar"

B、bat调用7zip解压实例一

@echo off
setlocal enabledelayedexpansion
echo please make sure you are admin!
echo 执行此脚本前,请确认已经安装解压缩程序7-Zip
:::::::::: 配置参数 ::::::::::
::解压程序WinRAR.exe所在目录 32位%ProgramFiles(x86)% 或64位 %ProgramFiles%
set 7ZipDir=%ProgramFiles%\7-Zip
REM 当前目录下用来测试的压缩包
set plegodemo=plegodemo.zip
:: 解压
set curdir=%~dp0
set deploydir=E:\mytest
set "plegodemodir=!plegodemo:.zip=!"
:: C:\01_MyApp\7-Zip\7z.exe x newPack.zip -oc:\Doc -aoa
start /wait "" "!7ZipDir!\7z.exe" x !curdir!!plegodemo! -o!deploydir!\!plegodemodir!\ -aoa
@rem 参考此命令,自己写了一个成功的用例,解压结果符合预期,十分完美:
"c:\path\7-zip\7z.exe" x d:\path\xx.zip -o"d:\xxx\path\\" -aoa
@rem 根据实践结果,此命令对 .7z 同样适用

:: 压缩
set "name=%date:~0,4%%date:~5,2%%date:~8,2%%time:~0,2%%time:~3,2%%time:~6,2%"
:: C:\01_MyApp\7-Zip\7z.exe a -t7z newPack.7z "F:\14_newWork\7z\testDoc\*" -r -mx=9 -m0=LZMA2 -ms=10m -mf=on -mhc=on -mmt=on

start /wait "" "!7ZipDir!\7z.exe" a -tzip !deploydir!\!plegodemodir!_%name%.zip "C:\inetpub\*" -r -mx=9

3、用7z来解压.tar.gz

zip软件可以实现文件的压缩和解压缩,
如果使用7z.exe对tar.gz文件进行解压缩,
需要经过两次解压,第一次解压成tar文件,
再解压一次生成源文件,
例如test.tar.gz解压一次生成test.tar。
再次解压生成test

测试editor.md的公式功能

$$ x={-b\pm \sqrt{b^2 - 4ac}\over 2a}$$

$$ x\href{why-equal.html}{=} y^2 + 1 $$

what does the red 'href' mean?

SSH

一、openssh升级

0、别人的笔记,本文参考来源

https://www.cnblogs.com/pengyong1211/p/10308658.html

1、下载最新版本 OpenSSH

wget https://openbsd.hk/pub/OpenBSD/OpenSSH/portable/openssh-7.8p1.tar.gz
目前最新版本是7.8,已经有openssh8.02

2、编译安装 OpenSSH

tar xzvf openssh-7.8p1.tar.gz
cd openssh-7.8p1
./configure --prefix=/usr --sysconfdir=/etc/ssh 

3、把老版本的ssh放到另外一个目录,这步是关键,这样就不用卸载旧版本

mkdir /etc/sshbak
mv  /etc/ssh/* /etc/sshbak/

4、编译源码包

make && make install

5、编译安装成功后,进行配置

cp  ./contrib/redhat/sshd.init  /etc/init.d/sshd ---启动文件
chkconfig --add sshd
chkconfig sshd on
修改配置文件  /etc/ssh/sshd_config
Port 22
PermitRootLogin yes
去掉这2行的注释 ,最后一个是允许root用户直接使用sshd服务登录服务器的!

6、启动

service  sshd  restart
使用ssh -V 命令查看一下,如果上面都没有报错的话,这里ssh的版本就是7.8了。
最关键的一步就是把老的ssh文件夹移到其他地方,然后新的ssh的目录仍然放在/etc/ssh下面,这样的升级过程中ssh就不会断开了,我这后面都是用这种方式升级openssh。

二、ssh 端口转发

http://wishcell.top/blog/2020/07/10/ssh端口转发

三、免密码登录

0、大致描述

对于自己名下的多台linux机器,每次登录输入密码十分烦琐。
如果想要不输入密码直接登录:
如A:192.168.1.2 作为一台编译服务器,需要在很多机器上登录

此时将其它机器的 ~/.ssh/id_rsa.pub 内容,copy到服务器的 authorized_keys 文件即可。
具体操作如下

1、找到配置服务器端配置文件 sshd_config

系统安装路径:
  /etc/ssh/sshd_config
自己采用默认配置安装:
  cd openssh-8.02 && ./configure && make -j4 && make install
  此时配置文件路径 /usr/local/etc/ssh/sshd_config

2、查看服务器 authorized_keys 配置

打开 sshd_config,找到 AuthorizedKeysFile,
它的默认配置是 .ssh/authorized_keys ,如果不确定 .ssh 的具体位置,可以直接将其写死:
/home/user/.ssh/authorized_keys

3、客户机生成密钥文件和私钥文件id_rsa,id_rsa.pub或id_dsa,id_dsa.pub

具体方法:到客户即上执行命令
ssh-keygen -t [rsa|dsa]
将会生成密钥文件和私钥文件 id_rsa,id_rsa.pub或
id_dsa,id_dsa.pub

如果采用 ssh-keygen -t rsa -f rsa_xxx 则可以指定输出文件名

4、同步.pub文件

A、手工,人肉同步

将 .pub 文件复制到服务器的 .ssh 目录, 并 cat id_dsa.pub >> ~/.ssh/authorized_keys

B、自动同步

$ ssh-copy-id -i ~/.ssh/id_rsa.pub username@server -p 22
$ ssh-copy-id -i ~/.ssh/id_rsa.pub "-p 10022 user@server"

5、重启 sshd 服务

有些服务器,可以使用命令:
   systemctl restart sshd
但是有些不行,直接手工重启,但必须在本机操作,不能远程:
  ps -ef | grep sshd  首先找到原来 sshd 课执行文件位置
  /usr/sbin/sshd -D
  然后将其kill掉,重新启动: /usr/sbin/sshd -D &

6、某些情况下,可能免密失败,仍需要密码,解决办法

A、常规方法,默认文件名

有时,登录服务器时,报错:
   `sign_and_send_pubkey: signing failed: agent refused operation`
然后还是要求输入账号密码。
此时,在客户端输入:
[user-dir] # eval  "$(ssh-agent -s)"
Agent pid 20392
[user-dir] # ssh-add
Identity added:/home/user/.ssh/id_rsa (user)
[user-dir] # ssh 192.168.1.2 -luser
login: Sat Oct 17 18:28:00 2020 from 192.168.1.3

B、特殊情况,指定文件名

ssh-keygen -f ~/.ssh/myname  
ssh-keygen -f ~/.ssh/myname  

提示:Your identification has been saved in /home/zorro/.ssh/myname  
Your public key has been saved in /home/zorro/.ssh/myname.pub  ---注1  (我这个命令生成的就是这个文件名)
之后  ssh-add ~/.ssh/myname (正常情况下 做到这里 再在web上upload就ok了)

好了 重点来了 这时候你需要看一下ssh_config 这个文件里的
IdentityFile ~/.ssh/identity
IdentityFile ~/.ssh/id_rsa ---这个是不是和注1 不一样?
IdentityFile ~/.ssh/id_dsa 

如果是, 当然我们最好不要改配置文件,那么
1.把你~/.ssh/下面生成的那俩文件 myname改成id_rsa
mv myname id_rsa
mv myname.pub id_rsa.pub
2.ssh-add  id_rsa.pub

7、别名登录

添加的位置:vim /etc/ssh/ssh_config
也可能,它包含了其它配置文件,在其它的配置文件中:
   include /etc/ssh/ssh_config.d/*.conf

# 其模板如下
Host <any name here>
  HostName <your server address>
  Port <22>
  User <username>
  IdentitiesOnly yes
# 一个具体的例子
Host aliyun
  HostName 192.168.21.43
  Port 22
  User vkso
  IdentitiesOnly yes

登录方式:
ssh aliyun
而且可以自动列出所有别名,相当方便

四、本人系统升级遇到的ssh连接诡异问题

原本是OK的,可以远程 ssh ip -luser 登录
将系统升了下级:
    yum -y install upgrade && yum -y install update
然后登录失败,输入用户名密码后,直接提示:
  permission denied
为此花了好多时间,22端口没有被 firewall 屏蔽,而且 sshd 也正常运行中

  最后尝试修改 sshd_config, 将默认端口22改成2222,再登录,竟然成功了

sqlite3

资源

1、核心源码

本地资源:sqlite-snapshot-202007301737.tar.gz
网站下载最新版本 https://www.sqlite.com/download.html

2、安装

A、linux安装

一般情况下,linux默认支持 sqlite3,如centos7.5
没有默认支持也没关系,简单的两条语句即搞定:
tar -xf sqlite-snapshot-202007301737.tar.gz
cd sqlite-snapshot-202007301737 
./configure && make && make install

B、windows安装

可以采用仿 linux 环境安装。
也可以不安装,直接放到项目中一并编译。
windows上的 sqlite 客户端十分好用

对tcl的支持
源码包中除了sqlite源码本身之外,还有一个 tcl 扩展接口。扩展库。
同时支持 windows 和 linux ,安装也十分方便。
readme.txt 说的清楚,也是简单的一句:
./configure && make && make install

一、图形界面查询操作

1、准确查询

select * from tableName where position=="myPosition"
然后点击下面的"Execute SQL"按钮

2、模糊匹配

select * from tableName where position like "%Position"

说明:where position中的position是表中的一个列名

多种查询方法,支持中文
select * from myTbl where name!="Mr.Zhou"
select * from 姓氏表 where 姓!="周"
select * from myTble where name=zhou
select * from myTble where name==zhou 一个等号和两个等号效果相同
select * from myTbl where name like "%甫" 能查询到皇甫,%是匹配关键字
select myTbl.pos from myTbl inner join secTbl on myTbl.name=secTbl.name  这样查询出的结果可能有重复
select distinct myTbl.pos from myTbl inner join secTbl on myTbl.name=secTbl.name  加上distinct关键字后,查询出的结果不再重复

sqlite3版权问题
已加入 public domain<br>就是任何人都可以随便使用

全部文档
sqlite.com/download.html
全部说明文档,压缩包约3.5M,或者到 runoob 上看sqlite也不错

linux C 操作数据库

https://blog.csdn.net/qq_30594349/article/details/53015708?utm_medium=distribute.pc_relevant_t0.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase

#include <stdio.h>
#include <sqlite3.h>
static sqlite3 *db = NULL;
static char *errmsg = NULL;
int main()
{
    int ret;
    int insert;
    ret = sqlite3_open("test.db", &db);
    if(ret)
    {
        printf("can not open database.\n");
    }
    else
    {
        printf("open database succsee.\n");
    }
    insert =  sqlite3_exec(db, "insert into test_table values('wujibing',111,222)", 0, 0, &errmsg);//插入
    //insert = sqlite3_exec(db, "delete from test_table where name='xu'", 0, 0, &errmsg);//删除
    //insert = sqlite3_exec(db, "delete from test_table", 0, 0, &errmsg);//清空
    printf("exec_ret: %d\n", insert);
    printf("errMsg: %d\n", errmsg);
    sqlite3_close(db);
    return 0;
}

#include <stdio.h>
#include <sqlite3.h>

static sqlite3 *db = NULL;
static char **Result = NULL;
static char *errmsg = NULL;

int main()
{
    int ret;
    int i;
    int j;
    int nrow;
    int ncolumn;

    ret = sqlite3_open("test.db", &db);
//    sqlite3_exec(db, ".head on", 0, 0, &errmsg);
    ret = sqlite3_get_table(db, "select * from test_table", &Result, &nrow, &ncolumn, &errmsg);
    if(ret)
    {
        printf("can not open database.\n");
    }
    else
    {
        printf("open database success.\n");
        for(i = 0; i <= nrow; i++)
        {
            for(j = 0; j < ncolumn; j++)
            {
                printf("%s|", Result[i * ncolumn + j]);
            }
            printf("\n");
        }
    }
    sqlite3_free_table(Result);
    sqlite3_close(db);

    return 0;
}

#include <stdio.h>
#include <sqlite3.h>
#include <stdlib.h>

static sqlite3 *db;

void create_table(sqlite3 *db)
{
    char *sql = NULL;
    char *errmsg = NULL;
    int ret;

    sql = "create table if not exists mytable (id integer primary key, name text)";
    ret = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
    if(ret != SQLITE_OK)
    {
         printf("create table error: %s\n", errmsg);
    }
}

void insert_record(sqlite3 *db)
{
    char sql[100];
    char *errmsg = NULL;
    int name;

    int ret;
    int id;

    printf("Please input phone1 and phone2:\n");
    printf("phone1: ");
    scanf("%d", &id);
    getchar();
    printf("name: ");
    scanf("%d", &name);
    getchar();

    sprintf(sql, "insert into test_table values(%d, %d)", id, name);

    ret = sqlite3_exec(db, sql, NULL, NULL, &errmsg);
    if(ret != SQLITE_OK)
    {
        printf("insert table error: %s\n", errmsg);
    }
}

int displaycb(void *para, int ncolumn, char ** columnvalue, char *columnname[])
{
    int i;

    printf("total column is %d\n", ncolumn);

    for(i = 0; i < ncolumn; i++)
    {
        printf("col_name: %s-----> col_value: %s\n", columnname[i], columnvalue[i]);
        printf("============================\n");

    }
    return 0;
}

void inquire_usecb(sqlite3 *db)
{
    char *sql = NULL;
    char *errmsg = NULL;
    int ret;

    sql = "select * from mytable";
    ret = sqlite3_exec(db, sql, displaycb, NULL, &errmsg);
    if(ret != SQLITE_OK)
    {
        printf("select is error: %s\n", errmsg);
    }
}

int main()
{
    int ret;

    ret = sqlite3_open("test.db", &db);

    sqlite3_exec(db,"select * from test_table",displaycb, NULL, NULL);
    insert_record(db);
    sqlite3_close(db);

    return 0;
}

windows下操作数据库

https://www.cnblogs.com/tfanalysis/p/4073756.html

一个sqlite包sqlite-snapshot-201612131847,里面包含sqlite,竟然还同时包含一个tcl引擎
sqlite配置两个主键列
create table tableName(
col1 date, col2 integer, col3 text, primary key(col1, col2))

关键技巧,血泪史
linux C 读取sqlite3数据库,当文件比较大时,比如超过1G,`sqlite3_exec(db, "select * from tableName", prcessCalBack, NULL, NULL) ` 返回就会十分慢,极其耗时,估计也正是因为这个原因,sqlite才搞了一个callBack函数,以便让用户能够尽快看到结果。
  可是对于刚刚接触数据库操作,理解还不深的同学。通常做法是,等函数执行结束,才执行显示操作。给人带来极差的用户体验。
     正确做法,应该要起多进程,将callBack函数放在别的进程中。因为sqlite3_exec将结果传输给回调的时间还是很短的。

【深入理解SQLite3之sqlite3_exec及回调函数_猪哥的专栏-CSDN博客_sqlite3_exec】 https://blog.csdn.net/u012351051/article/details/90382391

虽然文章写的不错,但没有深入研究。当数据达到百万条记录时,可以快速执行回调函数。但是sqlite3_exec函数内部会卡很久。这个问题该如何解决

【SQLite查询优化(转) - 不得闲 - 博客园】https://www.cnblogs.com/DxSoft/archive/2011/02/12/1952871.html  这个写的很细

 问题最终解决,解决办法是在windows上,用sqlite3客户端,在查询的关键字上创建了一个索引,数据库空间立即从1.5G变成1.9G,但是查询速度立即秒返回。

多表查询的各种玩法
select aTbl.* bTbl.* cTbl.*
from cTbl
inner join a.tbl on ( c.s1==a.s1 and c.s2 == a.s2)
inner join b.tbl on ( c.t1==b.t1 and c.t2 == b.t2)


大数据量查询时,采用==比较,效率比采用like要快太多,能明显感觉到。两千万条的记录,like 要等至少五秒,或者更多,采用等号(==)感觉无延时

查询重复的关键孜
1、参考资料
【SQL SQLite 筛选(查询)出 重复数据_我的分享,希望能帮到你-CSDN博客】
https://blog.csdn.net/shanxing200/article/details/52556570

2、具体操作
SELECT id AS 编号,
       title AS 影片名称,
       shortName AS 拼音,
       date AS 年份,
       actor AS 演员,
       type AS 类别,
       description AS 简介,
       filepath AS 路径,
       area AS 国家
 FROM mediainfo tempTable
 WHERE (tempTable.title) IN (
     SELECT title FROM mediainfo
     GROUP BY title
     HAVING count( * ) >= 2
     ORDER BY title
 );

亲自实践
可以按照任意一个关键孜来搜索
select * from myTbl group by myTbl.key1

逐步增加条件,至少两条记录
select * from myTbl group by myTbl.key1
having count(*) >=2

在排序一下
select * from myTbl group by myTbl.key1
having count(*) >=2 order by mytbl.key1

splint

一、安装

1、在网络环境、配置OK的系统,可以 yum 一键安装

2、某些情况无法一键安装,需要下载源码自己安装

下载splint-3.1.2.tar.gz:https://github.com/splintchecker/splint

3、安装

[wishcell@localhost]~/test/splint-master% autoreconf -vif
[wishcell@localhost]~/test/splint-master% ./configure
[wishcell@localhost]~/test/splint-master% make
....
e.o lclsyntable.o lcltokentable.o sort.o symtable.o lclinit.o shift.o lclscan.o lsymbol.o mapping.o -lfl
/usr/bin/ld: 找不到 -lfl
collect2: 错误:ld 返回 1
make[3]: *** [splint] 错误 1
make[3]: 离开目录“/home/wishcell/test/splint-master/src”
make[2]: *** [all] 错误 2
make[2]: 离开目录“/home/wishcell/test/splint-master/src”
make[1]: *** [all-recursive] 错误 1
make[1]: 离开目录“/home/wishcell/test/splint-master”
make: *** [all] 错误 2
[wishcell@localhost]~/test/splint-master%
解决 -lfl 报错问题:
[root@localhost splint-master]# sudo yum install flex
已加载插件:fastestmirror, langpacks
Loading mirror speeds from cached hostfile
 * base: mirrors.aliyun.com
 * extras: mirrors.aliyun.com
 * updates: mirrors.aliyun.com
正在解决依赖关系
--> 正在检查事务
---> 软件包 flex.x86_64.0.2.5.37-3.el7 将被 升级
---> 软件包 flex.x86_64.0.2.5.37-6.el7 将被 更新
--> 解决依赖关系完成
然后重新: ./configure && make 时间还挺长,最后仍然报错,还是找不到
原来需要安装开发包:
[root@localhost splint-master]# yum -y install flex-devel
再次 make ,很快完成
[root@localhost splint-master]# whereis splint
splint: /usr/local/bin/splint
[root@localhost splint-master]#

二、linux下面,超级严格的代码检查工具

1、基础用法,检验单个文件

A、示例代码

test.c:
int main()
{
    int a = 3;
    int b = 4;
    printf("a +b = %d", a +b);
    return 0;
}

B、用法

/usr/local/bin/splint test.c

2、检测工程,需要对复杂的目录结构下所有C文件检测

A、困难

此时,检测的每个C文件,都会要求包含其依赖的头文件。
即需要包含头文件。项目特别复杂,怎么办?

B、解决办法

可以信托项目中原来的 makefile 框架,在编译规则中,将生成
中间文件 .o 的操作,改为splint检测规则

C、举例

GTEST_DIR=./GTEST-1.7.0
CurrDir=$(shell pwd)
spline=/usr/bin/splint \
      -booltrue TRUE \
      -retvalint     \
      +bounds        \
      -type          \
      +boundsread    \
      +trytorecover  \
      -nullstate     \
      charindex      \
      -immediatetrans\
      -mustfreefresh \
      -nullret       \
      mustfreeonly   \
      -temptrans     \
      -dependenttrans\
      -boundswrite   \
      -bufferoverflowhigh \
      -retvalother   \
      -mayaliasunique\
      -exportlocal   \
      -initalelements\
      +ignoresigns   \
      +ignorequals   \
      +relaxtypes    \
      -predboolothres\
      -predboolint   \
      -globstate     \
      -unqualifiedtrans\
      -compdestroy   \
      -compdef       \
      -usedef        \
      -paramuse      \
      -nullassign    \
      -formattype    \
      -onlytrans     \
      -nullpass      \
      -usereleased   \
      -redef         \
      -compmempass   \
      -branchstate   \
      +posixlib      \
      -shiftnegative \
      -boolops       \
      -evalorder     \
      -unregcog      \
      -likelybool

PrjDir = ../
Module1= ../module_1/source
Module2= ../module_2/source
Module3= ../module_3/source

CaseDir = ./case
stubDir = ./stub/source

linux_env=$(shell getconf LONG_BIT)
ifeq($(linux_env), 32)
   DEFS=-DMACHINE_BIT32=32
else
   DEFS=-DMACHINE_BIT64=64
endif

INCS=-I../module_1/include \
     -I../module_2/include \
     -I../module_3/include \
     -I$(GTEST_DIR)/include\
     -I$(GTEST_DIR)/include/gtest \
     -I$(GTEST_DIR)/include/gtest/internal

vpath %c $(../module_1)
vpath %c $(../module_2)
vpath %c $(../module_3)

PrjSrc=$(notdir $(wildcard $(module1_dir)/*.c))\
       $(notdir $(wildcard $(module2_dir)/*.c))\
       $(notdir $(wildcard $(module3_dir)/*.c))
PrjOBJ=$(PrjSrc:%.c=$(PrjOBJDir%.x))
stubOBJ=$(stubSrc:%.c=$(StubOBJDir%.x))

#这里原来是做中间文件生成的,但是现在让它生成检测日志
$(PrjOBJDir)%.x : %.c
    @$(SPLINT) ${INCS}


lt; || echo '' $(TESTS): ${PrjObj} ${CaseObj} ${StubObj} echo '' .PHONY: clean clean: rm -f $(TESTS) *o *.gcda * 

三、强大的测试能力

1、典型示例

A、典型案例背景说明

对数据非法越界操作,连valgrind都无法检测,甚至一个warning都没有.
在splint的检测报告中,显然看到了对它的告警

B、示例代码

混用结构体中的 char data[0]; 和 char *data 成员。
造成特定类型cpu环境崩溃。在某些环境上,无异常症状

C、实例检测效果

i、先用 valgrind 做对比实验

valgrind --tool=memcheck -leak-check=full --max-stackframe=20000000 ./xx > log 2>&1

ii、splint 检测结果

在修改 splint 告警的过程中,必然会解决掉越界问题

snmp

一、centos7 linux snmp命令行工具安装

snmpget,snmpset,snmpwalk是SNMP的基本工具
yum -y install net-snmp-utils

二、linux自身作为 snmp 服务器

sudo apt-get install snmpd
sudo apt-get install snmp
service snmpd start
为了获取所有的数据,修/etc/snmp/snmpd.conf,添加最后一行
view systemonly included .1.3.6.1.2.1.1
view systemonly included .1.3.6.1.2.1.25.1
view systemonly included .1
service snmpd restart

三、 snmp工具包自带工具

1、snmpwalk

[root]# snmpwalk -v 2c public 127.0.0.1 system
[root]# snmpwalk -v 2c public 127.0.0.1 .1.3.6.1.2.1.1

2、snmpbulkwalk

[root]# snmpbulkget -v 2c public 127.0.0.1 .1.3.6.1.2.1.1
[root]# snmpbulkwalk -v 2c public 127.0.0.1 .1.3.6.1.2.1.1

3、snmpget

[root]# snmpget -v 2c public 127.0.0.1 .1.3.6.1.2.1.1

4、snmpset

5、帮助

linux命令行下,输入 snmp 然后 tab,会弹出所有的snmp命令

shttpd

[toc]

shttpd 上传文件后,如果想对数据处理一番,然后跳转到新的页面。正确做法是在建立开始传输文件时,回应一个完整的页面头给浏览器,然后一包一包地接收,直到接收完毕,并处理完成,再将跳转的完事web页面发送给浏览器。

web页面编码与后台编码不一致的问题

页面是gbk显示中文,但是传到后台,C中处理变成了乱码。将其改为utf-8,传到后台能成功识别,但是前台页面又变成了乱码。

解决方案

https://www.cnblogs.com/Marydon20170307/p/11362559.html
linux vim 环境下,查看文件编码,发现是cp936,需要将其转化为utf-8,然后将 shttpd example.c中,返回html内容的 example.c 转化为 utf-8格式。这样web浏览器中文显示正常,数据传到后台也能正常处理

iconv 转换cp936到utf-8

1.用vim打开文件,确保下面两个步骤

vim xx.txt
set ff=unix
set fileencoding=utf-8

2.用工具iconv转换

iconv -f gbk -t utf-8 -c xx.txt -o xx.txt.o
奇怪的是,转换之后,xx.txt和转换之前并无区别。但是此时xx.txt放到web页面上竟然显示正常了

指定根目录,后续资源默认都到根目录查找

可以对shttpd主函数进行改造,直接将部分参数写死,而不是从命令行传入,这样反而简单
int main() 或者在多进程中,将其函数名改为其它,如int webServer(). 在函数入口处,即自己构造参数,模拟手工输入:

int argc=3;
char *argv[10] =
{
     "webServer", //这是服务器的名字
     "-root",     //这是干什么的?难道是权限?
     "./web",     //指定当前路径下的web为根目录
0,0,0,0,0,0,0
};


对于shttpd的某些URL,需要指定处理函数,或者转发给脚本处理。在shttpd中,都是指定函数自己解决,如:

shttpd_register_uri(ctx, "/post.html", &receive_post_from_client, (void*)data
表示对于资源 127.0.0.1/post.html 的请示,将由函数
receive_post_from_client来处理。
而这个post.html 在网页中一般是什么样子呢?
实际上就是一个 \<form>, 可以是shttpd自己通过shttpd_printf()返回给前端的页面,也可以是web/xx.html中的内容。

场景一

shtpd_printf(arg, "%s", "<form id=\"upload\" method=\"post\" action=\"/post.html\" enctype=\"multipart/form-data\" >"
    "<input type=\"file\" ... />"
    "<input type=\"text\" ... />"
    "<input type=\"hidden\" ... />"    "<input type=\"submit\" ... />"
"</form>");


其中,method="post"表示传输方式,post加密,get为明文传输。action="/post.html",表示资源为127.0.0.1:port/post.html,也就是说要由 receive_post_from_client 来处理,这在前面服务器启动已经注册。写成action="post.html"效果也是同样的

场景二

在web/目录下,存在某个html文件,xx.html中有一段代码 :

<form id="upload" method="post" action="post.html" enctype="multipart/form-data>
   <input type="file" ... />
    <input type="text" ... />
   <input type="hidden" ... />
   <input type="submit" ... />
</form>


同样,这个文件被打开的时候:127.0.0.1:port/xx.html
只要点击控件中的提交按钮,或者在其它js 函数中,执行> 了$("#upload").submit操作,都能达到提交效果。将数据传输到后台

shttpd中C函数可以引用 js 文件

访问shttpd首页时,默认由show_index函数,输出 html 字符串。默认的样例中,只回复了基础的http协议报文
现在要让它支持更多功能,需要继续加大输出,用C来实现webServer,显得十分臃肿笨拙。但是前端还是能勉强展示出效果。

shttpd_printf(arg, "%s",
  "HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"
  "<html>"
  "<head>"
  "<meta http-equiv='Content-Type' content='text/html;charset=utf-8' />"
  "<style>div{float:left;border:2px;solid #000}" 
     "ol{margin:5; padding:0}"
    ".div-inline{margin:0 40px}"
  "</style>"
  "<script src=\"./jquery.min.js\">      "</script>"
  "<script src=\"./otherFunction.js\">   "</script>"
  "<script src=\"./someModule.js\">      "</script>"
  "</head>"
  "<body style=\"text-align:center; background-color:#d1d1e3\" >"
  "<br/>"
  "<h1>欢迎使用shttpd服务器</h1>"
);


在这里,已经将这么多功能抽象到 js 文件中,然后通过引用的方式来使用,极大的简化了代码.
同样,还有许多CSS是C写死的,也就当封装成CSS文件来引用。

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