Shell脚本学习(三)注释的用法

Shell脚本的注释

Shell脚本单行注释用#,这个我想大家应该都知道。如果要把一段代码全部注释掉,可以用如下方法

1
2
3
4
5
6
7
8
9
10
11
#!bin/bash
echo "我不是单行注释"
# echo "我是单行注释,你看不到我"
echo "我不是多行注释"
:<<COMMENT
echo "我是多行注释1,你看不到我"
echo "我是多行注释2,你看不到我"
COMMENT
echo "我没有看到多行注释1和多行注释2"

其实COMMENT可以随意命名,只要别跟中间的注释内容相同即可。当Shell脚本执行遇到:<<COMMENT,就不执行脚本了,一直到再碰到COMMENT后才重新开始执行脚本。如果忘记写COMMENT或者写错(由于已经不执行脚本了,所以即使写错也不会报错),则:<<COMMENT之后的脚步将都不会执行。

参考文章:

Shell脚本的文件注释模板

1
2
3
4
5
6
7
8
9
10
#!bin/bash
# ----------------------------------------------------------------------
# name: login.sh
# version: 1.0
# createTime: 2016-06-22
# description: shell脚本的功能描述
# author: birdben
# email: 191654006@163.com
# github: https://github.com/birdben
# ----------------------------------------------------------------------

这里推荐一个比较好的Shell代码规范

Shell脚本学习(二)制作SSH登录远程服务器的Shell脚本

Ubuntu环境需要安装expect安装包

1
sudo apt-get install expect

使用shell脚本自动ssh登录远程服务器

login.sh

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#!/usr/bin/expect -f
# 设置ssh连接的用户名
set user liuben
# 设置ssh连接的host地址
set host 10.211.55.4
# 设置ssh连接的port端口号
set port 9999
# 设置ssh连接的登录密码
set password admin
# 设置ssh连接的超时时间
set timeout -1
spawn ssh $user@$host -p $port
expect "*password:"
# 提交密码
send "$password\r"
# 控制权移交
interact
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 确定login.sh脚本有可执行权限
chmod +x login.sh
# 执行login.sh脚本
./login.sh
# 注意
不能按照习惯来用sh login.sh来这行expect的程序,会提示找不到命令,如下:
login.sh: line 3: spawn: command not found
couldn't read file "*password:": no such file or directory
login.sh: line 5: send: command not found
login.sh: line 6: interact: command not found
因为expect用的不是bash所以会报错。因为bash和expect的脚本指定了不同的脚本解释器
#!/usr/bin/expect -f
#!/bin/bash
执行的时候直接./login.sh就可以了。~切记!

参考文章:

Shell脚本学习(一)Shell命令基础

1
2
3
4
5
6
7
8
echo $SHELL
# $SHELL是一个环境变量,它记录用户所使用的Shell类型。你可以用命令:
Shell-name
# 来转换到别的Shell,这里Shell-name是你想要尝试使用的Shell的名称,如ash等。这个命令为用户又启动了一个Shell,这个Shell在最初登录的那个Shell之后,称为下级的Shell或子Shell。
exit
# 可以退出这个子Shell。

先来个简单的例子吧,也就是我们程序猿最长说的helloworld

helloworld.sh

1
2
3
4
5
6
#!/bin/bash
# 注意:"="号两边不能有空格,因为个人习惯问题,我就总喜欢在等号两边加上空格
demo="hello world"
# 在终端输出变量demo,也就是hello world
echo $demo

可以使用下面两种方式执行helloworld.sh,执行sh脚本之前需要检查权限问题,这里就不细说Linux权限问题了,否则执行会提示没有权限,具体请百度自行解决。

1
2
$ ./helloworld.sh
$ sh helloworld.sh

注意:这里使用不同的操作系统执行Shell脚本时,可能会遇到问题。拿我自己举例,我在Mac上能执行的Shell脚本可能在Ubuntu上无法执行,可能是因为Mac默认使用bash执行的,而Ubuntu默认使用的是sh执行的。简单一句话描述sh和bash的区别,sh是bash的“子集”,所以有的Shell脚本能用bash执行,而用sh执行就会报错。

解决办法

1
2
3
4
# 采用链接指向
$ ln -s /bin/bash /bin/sh
# 检查是否正确
$ ls -l /bin/sh

sh和bash的具体区别请参考:
http://www.cnblogs.com/hopeworld/archive/2011/03/29/1998488.html

变量

Shell Script是一种弱类型语言,使用变量的时候无需首先声明其类型。新的变量会在本地数据区分配内存进行存储,这个变量归当前的Shell所有,任何子进程都不能访问本地变量。这些变量与环境变量不同,环境变量被存储在另一内存区,叫做用户环境区,这块内存中的变量可以被子进程访问。变量赋值的方式是:

variable_name = variable_value

如果对一个已经有值的变量赋值,新值将取代旧值。取值的时候要在变量名前加$,$variable_name可以在引号中使用,这一点和其他高级语言是明显不同的。如果出现混淆的情况,可以使用花括号来区分,例如:

echo “Hi, $demos”

就不会输出“Hi, hello worlds”,而是输出“Hi,”。这是因为Shell把$demos当成一个变量,而$demos未被赋值,其值为空。正确的方法是:

echo “Hi, ${demo}s”

单引号中的变量不会进行变量替换操作。
关于变量,还需要知道几个与其相关的Linux命令。

env用于显示用户环境区中的变量及其取值;set用于显示本地数据区和用户环境区中的变量及其取值;unset用于删除指定变量当前的取值,该值将被指定为NULL;export命令用于将本地数据区中的变量转移到用户环境区。

echo不同参数的区别

1
2
3
4
5
6
# 不换行输出
echo "456"
# 换行输出
echo -n "123"
# 处理特殊字符输出(\n是换行且光标移至行首)
echo -e "a\ndddd"

数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#!/bin/bash
# 一对括号表示是数组,数组元素用“空格”符号分割开。
a=(1 2 3 4 5)
###### 获取 ######
echo "获取"
a=(1 2 3 4 5)
# 用${#数组名[@或*]} 可以得到数组长度
echo ${#a[@]}
echo ${#a[*]}
# 用${数组名[下标]} 可以得到指定下标的值,下标是从0开始
echo ${a[2]}
# 用${数组名[@或*]} 可以得到整个数组内容
echo ${a[@]}
echo ${a[*]}
###### 赋值 ######
echo "赋值"
a=(1 2 3 4 5)
# 直接通过 数组名[下标] 就可以对其进行引用赋值
a[1]=100
# 如果下标不存在,自动添加新一个数组元素
a[1000]=1000
echo ${a[*]}
echo ${#a[*]}
###### 删除 ######
echo "删除"
a=(1 2 3 4 5)
# unset 数组[下标] 可以清除相应的元素
unset a[1]
echo ${a[*]}
echo ${#a[*]}
# unset 数组[下标] 不带下标,清除整个数据。
unset a
echo ${a[*]}
echo ${#a[*]}
###### 截取 ######
echo "截取"
a=(1 2 3 4 5)
# 截取数组 ${数组名[@或*]:起始位置:长度},从下标0开始,截取长度为3,切片原先数组,返回是字符串,中间用“空格”分开
echo ${a[@]:0:3}
echo ${a[*]}
# 如果加上”()”,将得到切片数组,上面例子:c 就是一个新数据。
c=(${a[@]:1:4})
echo ${c[*]}
echo ${#c[*]}
###### 替换 ######
echo "替换"
a=(1 2 3 4 5)
# ${数组名[@或*]/查找字符/替换字符} 该操作不会改变原先数组内容,如果需要修改,可以看上面例子,重新定义数据。
echo ${a[@]/3/100}
echo ${a[@]}
# 如果需要需求,重新赋值给变量a
a=(${a[@]/3/100})
echo ${a[@]}
###### 根据分隔符拆分字符串为数组 ######
echo "根据分隔符拆分字符串为数组"
a="one,two,three,four"
# 要将$a按照","分割开,并且存入到新的数组中
OLD_IFS="$IFS"
IFS=","
arr=($a)
IFS="$OLD_IFS"
for s in ${arr[@]}
do
echo "$s"
done
# arr=($a)用于将字符串$a分割到数组$arr ${arr[0]} ${arr[1]} ... 分别存储分割后的数组第1 2 ... 项 ,${arr[@]}存储整个数组。变量$IFS存储着分隔符,这里我们将其设为逗号 "," OLD_IFS用于备份默认的分隔符,使用完后将之恢复默认。

if语句

if语句格式

1
2
3
4
5
6
7
if …; then
elif …; then
else
fi

与其他语言不同,Shell Script中if语句的条件部分要以分号来分隔。第三行中的[]表示条件测试,常用的条件测试有下面几种:

1
2
3
4
5
6
7
8
# 要注意条件测试部分中的空格。在方括号的两侧都有空格,在-f、-lt、=等符号两侧同样也有空格。如果没有这些空格,Shell解释脚本的时候就会出错。
[ -f "$file" ] : 判断$file是否是一个文件
[ -x "$file" ] : 判断$file是否存在且有可执行权限,同样-r测试文件可读性
[ -n "$a" ] : 判断变量$a是否有值
[ -z "$a" ] : 判断变量$a是否为空字符串
[ $a -lt 3 ] : 判断$a的值是否小于3,同样-gt和-le分别表示大于或小于等于
[ "$a" = "$b" ] : 判断$a和$b的取值是否相等
[ cond1 -a cond2 ] : 判断cond1和cond2是否同时成立,-o表示cond1和cond2有一成立

Shell逻辑运算符、逻辑表达式请参考:

if条件例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/bin/bash
# 打印终端命令行的所有参数
echo $*;
# 打印终端命令行的所有参数的个数
echo $#;
# 如果终端命令行的所有参数的个数小于3,就输出所有参数
if [ $# -lt 3 ]; then
echo $*;
else
echo $0;
echo "参数过多不在控制台显示";
fi

在Shell中,脚本名称本身是$0,剩下的依次是$0、$1、$2…、${10}、${11},等等。

  • $* : 表示命令行的所有参数,不包括$0,也就是说不包括文件名的参数列表
  • $# : 表示命令行参数的个数,不包括$0,其实也可以理解成是包括$0的索引下标

for,while,until循环语句

while循环语句格式

1
2
3
while [ cond1 ] && { || } [ cond2 ] …; do
done

for循环语句格式

1
2
3
4
5
6
7
for var in …; do
done
for (( cond1; cond2; cond3 )) do
done

until循环语句格式

1
2
3
until [ cond1 ] && { || } [ cond2 ] …; do
done

循环例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
#!/bin/bash
###### while循环例子1 ######
echo "while循环例子1";
i=10;
while [[ $i -gt 5 ]]; do
echo $i;
((i--));
done;
###### while循环例子2 ######
echo "while循环例子2";
# 循环读取/etc/hosts文件内容
while read line; do
echo $line;
done < /etc/hosts;
###### for循环例子1 ######
echo "for循环例子1";
for((i=1;i<=10;i++)); do
echo $i;
done;
###### for循环例子2 ######
echo "for循环例子2";
# seq 10 产生 1 2 3 。。。。10空格分隔字符串。
for i in $(seq 10); do
echo $i;
done;
###### for循环例子3 ######
echo "for循环例子3";
# 根据终端输入的文件名来检查当前目录该文件是否存在
for file in $*; do
if [ -f "$file" ]; then
echo "INFO: $file exists"
else
echo "ERROR: $file not exists"
fi
done;
###### until循环例子1 ######
echo "until循环例子1";
a=10;
until [[ $a -lt 0 ]]; do
echo $a;
((a--));
done;

case语句

case/esac语句格式

1
2
3
4
5
6
7
8
case var in
pattern 1 )
… ;;
pattern 2 )
… ;;
*)
… ;;
esac
1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
case $1 in
start | begin)
echo "start something"
;;
stop | end)
echo "stop something"
;;
*)
echo "Ignorant"
;;
esac

select交互语句

Bash提供了一种用于交互式应用的扩展select,用户可以从一组不同的值中进行选择。

select交互语句格式

1
2
3
select var in …; do
break;
done

例如,下面这段程序的输出是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#!/bin/bash
select ch in "begin" "end" "exit"; do
case $ch in
"begin")
echo "start something"
;;
"end")
echo "stop something"
;;
"exit")
echo "exit"
break;
;;
*)
echo "Ignorant"
;;
esac
done;
## 注意这里交互输入要输入1,2,3,而不是beign,end,exit
# $ sh demo.sh
# 1) begin
# 2) end
# 3) exit

function语句

function语句格式

1
2
3
4
5
6
7
8
9
10
11
12
定义函数格式一:
functionname()
{
}
定义函数格式二:
# 函数名前面多了个function关键字
function functionname()
{
}

函数使用例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/bin/bash
###### 函数定义 ######
echo "函数定义";
# 注意:所有函数在使用前必须定义。这意味着必须将函数放在脚本开始部分,直至shell解释器首次发现它时,才可以使用。调用函数仅使用其函数名即可。
function hello() {
echo "Hello!";
}
function hello_param() {
echo "Hello $1 !";
}
###### 函数调用 ######
# 函数调用
echo "函数调用";
hello;
###### 参数传递 ######
echo "函数传参调用";
hello_param ben;
###### 函数文件 ######
echo "函数文件调用";
# 调用函数文件,点和demo_call之间有个空格
. demo_call.sh;
# 调用函数
callFunction ben;
###### 载入和删除 ######
echo "载入和删除";
# 用unset functionname 取消载入
# unset callFunction;
# 因为已经取消载入,所以会出错
# callFunction ben;
###### 参数读取 ######
echo "参数读取";
# 参数读取的方式和终端读取参数的方式一样
funWithParam(){
echo "The value of the first parameter is $1 !"
echo "The value of the second parameter is $2 !"
echo "The value of the tenth parameter is $10 !"
echo "The value of the tenth parameter is ${10} !"
echo "The value of the eleventh parameter is ${11} !"
echo "The amount of the parameters is $# !"
echo "The string of the parameters is $* !"
}
funWithParam 1 2 3 4 5 6 7 8 9 34 73
###### 函数return ######
echo "函数return";
funWithReturn(){
echo "The function is to get the sum of two numbers..."
echo -n "Input first number: "
read aNum
echo -n "Input another number: "
read anotherNum
echo "The two numbers are $aNum and $anotherNum !"
return $(($aNum+$anotherNum))
}
funWithReturn
# 函数返回值在调用该函数后通过 $? 来获得
echo "The sum of two numbers is $? !"

函数文件demo_call.sh

1
2
3
4
5
#!/bin/bash
function callFunction() {
echo "callFunction $1 !";
return 1;
}

参考文章:

Maven总结(二)Maven构建可执行的jar包并且包含依赖jar包

插件总结

上一篇我们介绍了Maven如何构建可执行的jar包,主要使用了maven-jar-plugin和maven-dependency-plugin这两个Maven的插件,maven-jar-plugin负责构建jar包,maven-dependency-plugin负责导出所有依赖的jar包,这里我们使用了maven-assembly-plugin插件,这个插件可以将所有当前jar依赖的所有jar包打包到当前jar里面,这样就不需要导出所有依赖的jar包了,直接运行jar就可以了,实在太方便了 ^_^

Maven的pom.xml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.birdben</groupId>
<artifactId>birdDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>birdDemo</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.13</version>
<scope>system</scope>
<!-- ${basedir} 项目根目录 -->
<systemPath>${basedir}/lib/slf4j-api-1.7.13.jar</systemPath>
</dependency>
<!-- 其他jar包引用开始 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
</dependency>
<!-- 其他jar包引用j结束 -->
</dependencies>
<build>
<finalName>App</finalName>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<appendAssemblyId>false</appendAssemblyId>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<archive>
<manifest>
<mainClass>com.birdben.App</mainClass>
</manifest>
</archive>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>assembly</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
App.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.birdben;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import junit.framework.TestResult;
/**
* Hello world!
*/
public class App {
public static Logger logger = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
TestResult test = new TestResult();
logger.info("App start");
System.out.println("Hello World!");
logger.info("App end");
}
}
终端
1
2
# 运行jar
$ java -jar App.jar

参考文章:

Maven总结(一)Maven构建可执行的jar包

步骤

  1. 需要指定的可执行的class,需要有入口main方法
  2. 使用mvn package来构建jar包
  3. 通过java -jar App.jar来执行jar包

Maven的pom.xml配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.birdben</groupId>
<artifactId>birdDemo</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>birdDemo</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.13</version>
<scope>system</scope>
<!-- ${basedir} 项目根目录 -->
<systemPath>${basedir}/lib/slf4j-api-1.7.13.jar</systemPath>
</dependency>
<!-- 其他jar包引用开始 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
</dependency>
<!-- 其他jar包引用j结束 -->
</dependencies>
<build>
<finalName>App</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.4</version>
<configuration>
<archive>
<manifest>
<!-- 指定Main方法入口的class -->
<mainClass>com.birdben.App</mainClass>
<!-- 在jar包的MANIFEST.MF文件中生成Class-Path属性 -->
<addClasspath>true</addClasspath>
<!-- Class-Path 前缀 -->
<classpathPrefix>lib/</classpathPrefix>
</manifest>
<!-- 把本地的第三方jar包添加到MANIFEST.MF文件中,可以解压打包后的jar包查看MANIFEST.MF文件 -->
<!--
Class-Path: 指定当前jar包执行所依赖的classpath,包括本地的第三方jar包和maven引入的jar包
Class-Path: lib/slf4j-api-1.7.13.jar lib/junit-3.8.1.jar
Main-Class: 指定当前jar包的入口class
Main-Class: com.birdben.App
-->
<manifestEntries>
<Class-Path>lib/slf4j-api-1.7.13.jar</Class-Path>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!-- 拷贝依赖的jar包到lib目录 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<!-- ${project.build.directory} 构建目录,缺省为target -->
<outputDirectory>
${project.build.directory}/lib
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
App.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.birdben;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import junit.framework.TestResult;
/**
* Hello world!
*/
public class App {
public static Logger logger = LoggerFactory.getLogger(App.class);
public static void main(String[] args) {
TestResult test = new TestResult();
logger.info("App start");
System.out.println("Hello World!");
logger.info("App end");
}
}
MANIFEST.MF
1
2
3
4
5
6
7
Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Built-By: ben
Class-Path: lib/slf4j-api-1.7.13.jar lib/junit-3.8.1.jar
Created-By: Apache Maven 3.3.1
Build-Jdk: 1.8.0_65
Main-Class: com.birdben.App
终端
1
2
# 运行jar
$ java -jar App.jar
遇到问题和解决方法
  • Q1 : 执行java -jar App.jar,报错:找不到或无法加载主类XXX.XXXX.XXX
  • A1 : 添加CLASSPATH环境变量
1
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
  • Q2 : 执行java -jar App.jar,报错:Exception in thread “main” java.lang.NoClassDefFoundError: org/slf4j/LoggerFactory
    at com.birdben.App.(App.java:11)
    Caused by: java.lang.ClassNotFoundException: org.slf4j.LoggerFactory
    at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    … 1 more

  • A2 : 是因为执行jar的时候没有找到对应的依赖的第三方jar包,在打好的jar包中的MANIFEST.MF文件的Class-Path也未找到第三方jar包的路径。解决方法就是在中添加本地的第三方jar包到MANIFEST.MF文件中。

1
2
3
4
5
6
7
8
9
10
<!-- 把本地的第三方jar包添加到MANIFEST.MF文件中,可以解压打包后的jar包查看MANIFEST.MF文件 -->
<!--
Class-Path: 指定当前jar包执行所依赖的classpath,包括本地的第三方jar包和maven引入的jar包
Class-Path: lib/slf4j-api-1.7.13.jar lib/junit-3.8.1.jar
Main-Class: 指定当前jar包的入口class
Main-Class: com.birdben.App
-->
<manifestEntries>
<Class-Path>lib/slf4j-api-1.7.13.jar</Class-Path>
</manifestEntries>

参考文章:

Git配置多个SSH-Key

之前周末在家使用github创建SSH-Key进行blog的提交,但是第二天在用公司,使用公司的GitLab提交代码时发现账号是我github的账号,我想肯定是github生成的SSH-Key把之前我公司GitLab的SSH-Key给覆盖了

查看我所有SSH-Key
1
2
3
4
5
6
7
8
$ cd ~/.ssh/
$ ls
github_rsa.pub
github_rsa
id_rsa.pub
id_rsa
known_hosts.old
known_hosts

这里一共有两个SSH-Key,一个github_rsa是我github的SSH-Key,id_rsa是我公司的GitLab的SSH-Key,因为我周末给自己的github博客创建了一个新的SSH-Key,直接使用的默认路径(~/.ssh/id_rsa.pub),所以就直接把我公司GitLab的SSH-Key给覆盖掉了

这次为了区分开我自己github和公司的GitLab的SSH-Key,在生成SSH-Key文件的时候,我用了不同的名称来区分

公司的GitLab生成一个SSH-Key
1
2
# 在~/.ssh/目录会生成gitlab_id-rsa和gitlab_id-rsa.pub私钥和公钥。我们将gitlab_id-rsa.pub中的内容粘帖到公司GitLab服务器的SSH-key的配置中。
$ ssh-keygen -t rsa -C "XXXXXX@XXX.com” -f ~/.ssh/gitlab_id-rsa
公司的GitLab生成一个SSH-Key
1
2
# 在~/.ssh/目录会生成github_id-rsa和github_id-rsa.pub私钥和公钥。我们将github_id-rsa.pub中的内容粘帖到github服务器的SSH-key的配置中。
$ ssh-keygen -t rsa -C "191654006@163.com” -f ~/.ssh/github_id-rsa
在~/.ssh目录下添加config配置文件用于区分多个SSH-Key
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 添加config配置文件
# vi ~/.ssh/config
# 文件内容如下:
# gitlab
Host gitlab.com
HostName gitlab.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/gitlab_id-rsa
# github
Host github.com
HostName github.com
PreferredAuthentications publickey
IdentityFile ~/.ssh/github_id-rsa
# 配置文件参数
# Host : Host可以看作是一个你要识别的模式,对识别的模式,进行配置对应的的主机名和ssh文件
# HostName : 要登录主机的主机名
# User : 登录名
# IdentityFile : 指明上面User对应的identityFile路径
再次查看目录结构
1
2
3
4
5
6
7
8
$ cd ~/.ssh/
$ ls
github_id-rsa.pub
github_id-rsa
gitlab-id_rsa.pub
gitlab-id_rsa
known_hosts.old
known_hosts

再次执行git命令已经不再提示权限验证问题

OK,大功告成

参考文章:

Spring的JDBCTemplate批量更新的性能问题

这两天偶然发现了一个JDBCTemplate批量更新的性能问题,问题是这样的一次性批量删除并且插入3000条记录,居然用了400s我也是被吓到了。我也看了一下代码确实用的是jdbcTemplate的batchUpdate方法,用法都没有错,正常执行SQL批量更新3000条数据应该也是秒级的,但是不清楚为什么使用jdbcTemplate的性能会和预期差这么多呢,后来经过反复测试才发现原来是参数类型惹的祸,具体请看下面的代码片段。

修改前的jdbcTemplate.batchUpdate代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void batchUpdateAcl(List<Auth> authBeans) {
List<Object[]> deleteParams = new ArrayList<Object[]>();
List<Object[]> insertParams = new ArrayList<Object[]>();
for (Auth auth : authBeans) {
String resourceID = auth.getResourceID();
String targetID = auth.getTargetID();
String targetType = auth.getType() == null ? AuthTargetType.ROLE.name() : auth.getType().name();
int acl = AuthEnum.formateAclCode(auth.getAcls());
deleteParams.add(new Object[] { targetID, resourceID, targetType });
insertParams.add(new Object[] { targetID, resourceID, targetType, acl });
}
String sql = "DELETE FROM " + TABLE_NAME + " WHERE target=? AND resource=? and targetType=?";
// 批量删除修改前的调用方式
jdbc.batchUpdate(sql, deleteParams);
sql = "INSERT INTO " + TABLE_NAME + " (target, resource, targetType, acl) VALUES(?,?,?,?)";
// 批量插入修改前的调用方式
jdbc.batchUpdate(sql, insertParams);
}

修改后的jdbcTemplate.batchUpdate代码片段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public void batchUpdateAcl(List<Auth> authBeans) {
final List<Object[]> deleteParams = new ArrayList<Object[]>();
final List<Object[]> insertParams = new ArrayList<Object[]>();
for (Auth auth : authBeans) {
String resourceID = auth.getResourceID();
String targetID = auth.getTargetID();
String targetType = auth.getType() == null ? AuthTargetType.ROLE.name() : auth.getType().name();
int acl = AuthEnum.formateAclCode(auth.getAcls());
deleteParams.add(new Object[] { targetID, resourceID, targetType });
insertParams.add(new Object[] { targetID, resourceID, targetType, acl });
}
String sql = "DELETE FROM " + TABLE_NAME + " WHERE target=? AND resource=? and targetType=?";
// 批量删除修改后的调用方式
jdbc.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Object[] args = deleteParams.get(i);
ps.setString(1, (String) args[0]);
ps.setString(2, (String) args[1]);
ps.setString(3, (String) args[2]);
}
@Override
public int getBatchSize() {
return 0;
}
});
sql = "INSERT INTO " + TABLE_NAME + " (target, resource, targetType, acl) VALUES(?,?,?,?)";
// 批量插入修改后的调用方式
jdbc.batchUpdate(sql, new BatchPreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps, int i) throws SQLException {
Object[] args = insertParams.get(i);
ps.setString(1, (String) args[0]);
ps.setString(2, (String) args[1]);
ps.setString(3, (String) args[2]);
ps.setString(4, (String) args[3]);
}
@Override
public int getBatchSize() {
return 0;
}
});
}

具体调用的方式有些变化,原来的调用方式jdbc.batchUpdate(sql, deleteParams);的参数是List\类型,而修改后的调用方式是实现的new BatchPreparedStatementSetter()接口的方法,只是这里的区别,但是性能却相差甚远。

Hexo + Github搭建个人博客

Hexo简介

hexo是一款基于Node.js的静态博客框架,官网地址:https://hexo.io/zh-cn/

安装环境

安装Git

程序猿应该基本都会用git了,我在这里就不详细介绍了。

  • 建立新的Repository,仓库名为【your_user_name.github.io】
  • 后续想要把网站部署到Github上,需要在【your_user_name.github.io】此仓库下的Setting配置中添加一个Deploy keys

Generating a new SSH key可以参考:

安装NodeJS

  • 因为我是Mac笔记本,所以可以使用homebrew安装
  • 也可以下载并安装NodeJS,官网地址:https://nodejs.org/

安装Hexo过程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 使用npm安装Hexo
$ npm install -g hexo-cli
# 初始化Hexo
$ hexo init
# 复制一个markdown文章到{Hexo}/source/_posts目录下
$ cp /Users/ben/Linux常用命令.md {Hexo}/source/_posts/Linux常用命令.md
# 生成静态页面
$ hexo generate
# 启动本地服务,浏览器访问:http://localhost:4000
$ hexo server
# 配置Hexo的_config.yml
deploy:
type: git
repo:https://github.com/birdben/birdben.github.io.git
branch:master
# 要提交到Github上需要安装hexo-deployer-git插件
$ npm install hexo-deployer-git --save
# 部署网站到Github上
$ hexo deploy
# 访问博客地址:http://birdben.github.io/
# 大功告成!!

部署步骤

每次部署的步骤,可按以下三步来进行。

  • hexo clean
  • hexo generate
  • hexo deploy

更换主题

1
2
3
4
5
6
7
8
9
10
11
# 从Github下载主题
$ git clone https://github.com/A-limon/pacman.git {Hexo}/themes/pacman
# 修改_config.yml中的theme配置
theme : pacman
# 重新生成静态页面
$ hexo generate
# 启动本地服务,浏览器访问:http://localhost:4000
$ hexo server

建站目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 初始化网站目录
$ hexo init <folder>
$ cd <folder>
$ npm install
# 执行完npm install命令后,会生成如下的目录结构
.
├── .deploy #需要部署的文件
├── node_modules #Hexo插件
├── public #生成的静态网页文件
├── scaffolds #模板
├── source #博客正文和其他源文件, 404 favicon CNAME 等都应该放在这里。Markdown 和 HTML 文件会被解析并放到 public 文件夹,而其他文件会被拷贝过去。
| ├── _drafts #草稿
| └── _posts #文章
├── themes #主题
├── _config.yml #全局配置文件
└── package.json

配置

网站的具体配置,请参考官网:https://hexo.io/zh-cn/docs/configuration.html

命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# init : 新建一个网站。如果没有设置folder,Hexo默认在目前的文件夹建立网站。
$ hexo init [folder]
# new : 新建一篇文章。如果没有设置 layout 的话,默认使用 _config.yml 中的 default_layout 参数代替。如果标题包含空格的话,请使用引号括起来。
$ hexo new [layout] <title>
# generate : 生成静态文件。
参数:
-d, --deploy : 文件生成后立即部署网站
-w, --watch : 监视文件变动
$ hexo generate
# publish : 发表草稿。
$ hexo publish [layout] <filename>
# server : 启动服务器。默认情况下,访问网址为: http://localhost:4000/。
$ hexo server
参数:
-p, --port : 重设端口
-s, --static : 只使用静态文件
-l, --log : 启动日记记录,使用覆盖记录格式
# deploy : 部署网站。
$ hexo deploy
参数:
-g, --generate : 部署之前预先生成静态文件
# render : 渲染文件。
$ hexo render <file1> [file2] ...
参数:
-o, --output : 设置输出路径
# migrate : 从其他博客系统迁移内容。
$ hexo migrate <type>
# clean : 清除缓存文件 (db.json) 和已生成的静态文件 (public)。
$ hexo clean
# list : 列出网站资料。
$ hexo list <type>
# version : 显示 Hexo 版本。
$ hexo version
# 安全模式,在安全模式下,不会载入插件和脚本。当您在安装新插件遭遇问题时,可以尝试以安全模式重新执行。
$ hexo --safe
# 调试模式,在终端中显示调试信息并记录到 debug.log。当您碰到问题时,可以尝试用调试模式重新执行一次,并 提交调试信息到 GitHub。
$ hexo --debug
# 简洁模式,隐藏终端信息。
$ hexo --silent
# 自定义配置文件的路径,执行后将不再使用 _config.yml。
$ hexo --config custom.yml
# 显示草稿,显示 source/_drafts 文件夹中的草稿文章。
$ hexo --draft
# 自定义 CWD,自定义当前工作目录(Current working directory)的路径。
$ hexo --cwd /path/to/cwd

Hexo添加管理博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 新建博文,其中postName是博文题目,新的博文会自动生成在博客目录下source/_posts/postName.md
$ hexo new "postName"
# 文件自动生成格式如下:
---
# 博文题目
title: "It Starts with iGaze: Visual Attention Driven Networkingwith Smart Glasses"
# 生成时间
date: 2014-11-21 11:25:38
# 标签, 多个标签可以使用[]数组格式
tags:[tag1, tag2, tag3]
# 分类, 多个分类可以使用[]数组格式
categories:[cat1,cat2,cat3]
---
正文, 使用 Markdown 语法书写

Hexo添加统计分析工具

设置birdben.github.io/themes/yilia/_config.yml
1
2
3
4
5
6
7
8
9
10
# Miscellaneous
google_analytics:
enable: false
## e.g. UA-82900755-1 your google analytics ID.
id: UA-82900755-1
## e.g. yangjian.me your google analytics site or set the value as auto.
site: auto
cnzz_tongji:
enable: true
siteid: 1260188951

添加Google Analytics分析工具

birdben.github.io/themes/yilia/layout/_partial/google-analytics.ejs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<% if (theme.google_analytics){ %>
<!-- Google Analytics -->
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-82900755-1', 'auto');
ga('send', 'pageview');
</script>
<!-- End Google Analytics -->
<% } %>

登录Google Analytics复制如图中的统计代码,保存到google-analytics.ejs文件中,在birdben.github.io/themes/yilia/_config.yml文件中设置Google Analytics的跟踪ID

Google Analytics统计

添加CNZZ分析工具

birdben.github.io/themes/yilia/layout/_partial/head.ejs
1
2
# 修改head.ejs,在</head>前面添加如下代码
<%- partial('cnzz-analytics') %>
birdben.github.io/themes/yilia/layout/_partial/cnzz-analytics.ejs
1
2
3
4
5
<% if (theme.cnzz_tongji.enable){ %>
<script type="text/javascript">
var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");document.write(unescape("%3Cspan id='cnzz_stat_icon_1260188951'%3E%3C/span%3E%3Cscript src='" + cnzz_protocol + "s4.cnzz.com/z_stat.php%3Fid%3D1260188951' type='text/javascript'%3E%3C/script%3E"));
</script>
<% } %>

登录CNZZ复制如图中的统计代码,保存到cnzz-analytics.ejs文件中,在birdben.github.io/themes/yilia/_config.yml文件中设置CNZZ的siteid

CNZZ统计

参考文章:

Docker实战(十六)Docker安装HBase环境

HBase安装
1
2
3
4
5
6
7
# 下载HBase
$ wget http://apache.fayea.com/hbase/0.98.19/hbase-0.98.19-hadoop2-bin.tar.gz
# 解压HBase压缩包
$ tar -zxvf hbase-0.98.19-hadoop2-bin.tar.gz
# 需要修改下面HBase相关的配置文件
Dockerfile文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
############################################
# version : birdben/hbase:v1
# desc : 当前版本安装的hbase
############################################
# 设置继承自我们创建的 jdk7 镜像
FROM birdben/jdk7:v1
# 下面是一些创建者的基本信息
MAINTAINER birdben (191654006@163.com)
# 设置环境变量,所有操作都是非交互式的
ENV DEBIAN_FRONTEND noninteractive
# 添加 supervisord 的配置文件,并复制配置文件到对应目录下面。(supervisord.conf文件和Dockerfile文件在同一路径)
COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf
# 设置 hbase 的环境变量,若读者有其他的环境变量需要设置,也可以在这里添加。
ENV HBASE_HOME /software/hbase-0.98.19
ENV PATH ${HBASE_HOME}/bin:$PATH
# 复制 hbase-0.98.19 文件到镜像中(hbase-0.98.19 文件夹要和Dockerfile文件在同一路径),这里直接把上一篇Hadoop环境直接用上了
ADD hbase-0.98.19 /software/hbase-0.98.19
# 授权HBASE_HOME路径给admin用户
RUN sudo chown -R admin /software/hbase-0.98.19
# 容器需要开放HBase 60010端口
EXPOSE 60010
# 执行supervisord来同时执行多个命令,使用 supervisord 的可执行路径启动服务。
CMD ["/usr/bin/supervisord"]
Dockerfile源文件链接:

https://github.com/birdben/birdDocker/blob/master/hbase/Dockerfile

supervisor配置文件内容
1
2
3
4
5
6
7
8
9
10
11
12
# 配置文件包含目录和进程
# 第一段 supervsord 配置软件本身,使用 nodaemon 参数来运行。
# 第二段包含要控制的 2 个服务。每一段包含一个服务的目录和启动这个服务的命令。
[supervisord]
nodaemon=true
[program:sshd]
command=/usr/sbin/sshd -D
[program:hbase]
command=/software/hbase-0.98.19/bin/start-hbase.sh
配置HBASE_HOME/conf/hbase-env.sh
1
2
3
4
5
export JAVA_HOME=/software/jdk7
export HBASE_CLASSPATH=/software/hbase-0.98.19/lib/
export HBASE_IDENT_STRING=my
export HBASE_PID_DIR=${HBASE_HOME}/tmp
export HBASE_MANAGES_ZK=true
配置HBASE_HOME/conf/hbase-site.xml,hbase-default.xml并不在conf目录, 需要从./src/main/resources/目录拷贝并且改名为hbase-site.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?xml version="1.0"?>
<?xml-stylesheet type="text/xsl" href="configuration.xsl"?>
<configuration>
<property>
<name>hbase.rootdir</name>
<!-- 使用本地文件系统 -->
<value>file:///software/hbase-0.98.19/hbase_dir</value>
<!-- 使用本地的HDFS文件系统 -->
<!-- <value>hdfs://Ben:9000/hbase</value> -->
</property>
<property>
<name>hbase.zookeeper.property.dataDir</name>
<value>/software/hbase-0.98.19/zookeeper_dir</value>
</property>
</configuration>
控制台终端
1
2
3
4
# 构建镜像
$ docker build -t="birdben/hbase:v1" .
# 执行已经构件好的镜像,挂载在宿主机器的存储路径也不同,-h设置hostname,Hadoop配置文件需要使用
$ docker run -p 9999:22 -p 60010:60010 -t -i 'birdben/hbase:v1'
supervisor无法监控hbase
1
supervisor监控hbase不成功,是因为supervisor启动的程序必须是非daemon的启动方式,之前的文章已经反复提过好几次这个问题,但是${HBASE_HOME}/bin/start-hbase.sh的启动脚本,实际上是调用了hbase-daemon.sh的daemon启动脚本,所以这里supervisor启动hbase后,hbase进程的状态仍然是hbase (exit status 0; expected),是因为无法监控hbase进程,但是不影响hbase的正常启动和使用
使用hbase shell登录HBase服务器进行操作
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 通过ssh远程连接使用admin账号远程登录HBase的Docker容器
$ cd /software/hbase-0.98.19/bin/
$ ./hbase shell
2016-06-30 06:03:57,427 INFO [main] Configuration.deprecation: hadoop.native.lib is deprecated. Instead, use io.native.lib.available
HBase Shell; enter 'help<RETURN>' for list of supported commands.
Type "exit<RETURN>" to leave the HBase Shell
Version 0.98.19-hadoop2, r1e527e73bc539a04ba0fa4ed3c0a82c7e9dd7d15, Fri Apr 22 19:07:24 PDT 2016
hbase(main):001:0>
# 查询所有的Table
hbase(main):001:0> list
# 创建新表
hbase(main):001:0> create 'test','name'
0 row(s) in 0.5780 seconds
=> Hbase::Table - test
# 再次查询所有的Table
hbase(main):001:0> list
TABLE
test
1 row(s) in 0.0100 seconds
=> ["test"]
浏览器查看
1
2
# 查看HBase:
http://10.211.55.4:60010/master-status
启动hbase可能会报错 Address already in use
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
master: java.net.BindException: Address already in use
master: at sun.nio.ch.Net.bind(Native Method)
master: at sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:124)
master: at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:59)
master: at sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:52)
master: at org.apache.zookeeper.server.NIOServerCnxnFactory.configure(NIOServerCnxnFactory.java:111)
master: at org.apache.zookeeper.server.quorum.QuorumPeerMain.runFromConfig(QuorumPeerMain.java:130)
master: at org.apache.hadoop.hbase.zookeeper.HQuorumPeer.runZKServer(HQuorumPeer.java:73)
master: at org.apache.hadoop.hbase.zookeeper.HQuorumPeer.main(HQuorumPeer.java:63)
# 报错的原因是Zookeeper的端口被占用了或者Zookeeper已经启动了。
export HBASE_MANAGES_ZK=true
# 这个参数表示启动hbase之前自动启动zk
# 解决的办法有2种:
1.启动hbase的之前kill掉所有的zk进程,让hbase启动zk
2.将参数HBASE_MANAGES_ZK 改成false
# 在hbase之前手动启动zk
# 我这里使用的是方式一,可能之前zk的2181端口被其他应用占用了,所以kill掉所有占用2181端口的进程,再启动hbase就好用了

参考文章:

Linux常用命令(三)

curl命令

以下的案例都使用的百度开源API进行的测试,具体地址如下:
https://www.juhe.cn/docs/api/id/39

名称 类型 必填 说明
cityname string Y 城市名或城市ID,如:”苏州”,需要utf8 urlencode
dtype string N 返回数据格式:json或xml,默认json
format int N 未来6天预报(future)两种返回格式,1或2,默认1
key string Y 你申请的key
名称 类型 必填 说明
key string 应用APPKEY
type string 类型:top(头条,默认),shehui(社会),guonei(国内),guoji(国际),yule(娱乐),tiyu(体育)junshi(军事),keji(科技),caijing(财经),shishang(时尚)
curl -d 传递请求数据
1
2
3
# 参数:
# -G:指定GET方式发送请求
# --data/-d:指定使用POST方式传递数据
curl 发送GET请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 方式一:curl 以?方式传递参数发送GET请求
# 默认curl使用POST方式请求数据,这种方式下直接通过URL传递数据,也就是以?方式添加到url后面
# 格式:curl "https://hostname.com:8080/getMethod?param1=value1&param2=value2"
# 方式二:curl -G -d 发送GET请求
# 但是如果有-d参数,也可以加上-G参数指定使用GET方式请求数据,如果不加上-G则默认使用的POST方式请求数据
# 格式:curl -G -d "param1=value1&param2=value2" "http://hostname.com:8080/getMethod"
# 使用?方式传递参数,不使用-d方式
$ curl "http://v.juhe.cn/weather/index?format=2&cityname=%E8%8B%8F%E5%B7%9E&key=您申请的KEY"
# 也可以使用-d方式传递参数,但是必须指定-G来指定GET方式请求数据,因为该接口只支持GET方式请求,如果不加-G参数,使用-d参数就默认为POST方式请求
$ curl -G -d "format=2&cityname=%E8%8B%8F%E5%B7%9E&key=您申请的KEY" "http://v.juhe.cn/weather/index"
# 以下的方式就会报错,因为该接口只支持GET方式请求,不支持POST方式请求,如果不加-G参数,使用-d参数就默认为POST方式请求
$ curl -d "format=2&cityname=%E8%8B%8F%E5%B7%9E&key=您申请的KEY" "http://v.juhe.cn/weather/index"
# 返回的结果是error_code:203901,获取不到cityname参数
{"resultcode":"201","reason":"Error Cityname!","result":null,"error_code":203901}
curl 发送POST请求
1
2
3
4
5
6
7
8
9
10
11
12
13
# 格式:curl -d/--data "param1=value1&param2=value2" "http://hostname.com:8080/html/postMethod"
$ curl -d "type=tiyu&key=申请的KEY" "http://v.juhe.cn/toutiao/index"
$ curl --data "type=tiyu&key=申请的KEY" "http://v.juhe.cn/toutiao/index"
# POST提交多个同名参数,paramList是一个数组参数
$ curl -XPOST 'http://hostname.com/postMethod' -d 'userName=birdben&paramList=["aa", "bb"]'
# form表单上传文件
# 可以通过 --form/-F 方式指定使用POST方式传递数据
# --form/-F 参数相当于设置form表单的method="POST"和enctype="multipart/form-data"两个属性
# filename为文本框中的name元素对应的属性值。<type="text" name="filename">
$ curl --form "filename=@filename.txt" "http://hostname.com:8080/html/uploadFile"
$ curl -F "filename=@filename.txt;type=text/plain" "http://hostname.com:8080/html/uploadFile"
curl 特殊字符处理
1
2
3
4
5
# 默认情况下,通过GET/POST方式传递过去的数据中若有特殊字符(或者中文字符),首先需要将特殊字符转义在传递给服务器端,如"value 1"值中包含有空格,则需要先将空格转换成%20,如:
$ curl -d "value%201" "http://hostname.com"
# 在新版本的curl中,提供了新的选项 --data-urlencode,通过该选项提供的参数会自动转义特殊字符。
$ curl --data-urlencode "value 1" "http://hostname.com"
curl 指定其它协议
1
2
# 除了使用GET和POST协议外,还可以通过 -X 选项指定其它协议,一般用过ES删除索引的时候用的比较多,如:
$ curl -X DELETE "https://hostname.com"
curl 设置http请求头信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# 以下操作可以挂代理抓包查看并且验证
# -v 通过使用 -v 和 -trace获取更多的链接信息
# 访问https://www.github.com/下面返回的信息会提示该域名已经被重定向到https://github.com/
$ curl -v "https://www.github.com/"
* Rebuilt URL to: https://www.github.com/
* Hostname was NOT found in DNS cache
* Trying 192.30.252.130...
* Connected to www.github.com (192.30.252.130) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
* Server certificate: github.com
* Server certificate: DigiCert SHA2 Extended Validation Server CA
* Server certificate: DigiCert High Assurance EV Root CA
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: www.github.com
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Content-length: 0
< Location: https://github.com/
< Connection: close
<
* Closing connection 0
# -A 设置http请求头User-Agent,可以指定自己这次访问所模拟的浏览器信息
# 前一个直接用curl命令的,User-Agent: curl/7.37.1
> User-Agent: curl/7.37.1
# 使用-A参数重新指定浏览器信息后,User-Agent: Mozilla/5.0 Firefox/21.0
> User-Agent: Mozilla/5.0 Firefox/21.0
$ curl -v -A "Mozilla/5.0 Firefox/21.0" "https://www.github.com/"
Hostname was NOT found in DNS cache
* Trying 192.30.252.120...
* Connected to www.github.com (192.30.252.120) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
* Server certificate: github.com
* Server certificate: DigiCert SHA2 Extended Validation Server CA
* Server certificate: DigiCert High Assurance EV Root CA
> GET / HTTP/1.1
> User-Agent: Mozilla/5.0 Firefox/21.0
> Host: www.github.com
> Accept: */*
>
< HTTP/1.1 301 Moved Permanently
< Content-length: 0
< Location: https://github.com/
< Connection: close
<
* Closing connection 0
# -e 设置http请求头Referer,解释:检查http访问的Referer是服务器端常用的限制方法。比如你先访问网站的首页,再访问里面所指定的下载页,这第二次访问的referer地址就是第一次访问成功后的页面地址。这样,服务器端只要发现对下载页面某次访问的referer地址不是首页的地址,就可以断定那是个盗链了。所以这里我们可以自己修改我们的Referer链接地址。
$ curl -v -e "http://birdben.com/" "http://birdben.com/download.html"
* Hostname was NOT found in DNS cache
* Trying 50.63.202.46...
* Connected to birdben.com (50.63.202.46) port 80 (#0)
> GET /download.html HTTP/1.1
> User-Agent: curl/7.37.1
> Host: birdben.com
> Accept: */*
> Referer: http://birdben.com/
>
< HTTP/1.1 302 Found
< Connection: close
< Pragma: no-cache
< cache-control: no-cache
< Location: /download.html
<
* Closing connection 0
# --header/-H 设置header
$ curl -H "Connection:keep-alive" -H "Host:127.0.0.1" -H "Accept-Language:es" -H "Cookie:ID=1234" "https://www.github.com/"
* Hostname was NOT found in DNS cache
* Trying 192.30.252.129...
* Connected to www.github.com (192.30.252.129) port 443 (#0)
* TLS 1.2 connection using TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
* Server certificate: github.com
* Server certificate: DigiCert SHA2 Extended Validation Server CA
* Server certificate: DigiCert High Assurance EV Root CA
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Accept: */*
> Connection:keep-alive
> Host:127.0.0.1
> Accept-Language:es
> Cookie:ID=1234
>
< HTTP/1.1 301 Moved Permanently
< Content-length: 0
< Location: https://github.com/
< Connection: close
<
* Closing connection 0
# -I 设置http响应头处理,仅仅返回header
$ curl -I "https://github.com"
# --dump-header/-D 将http header保存到/tmp/header文件
$ curl -D /tmp/header "https://github.com"
curl -o/-O 下载
1
2
3
4
5
6
7
8
9
# 通过-o/-O选项保存下载的文件到指定的文件中:
-o:将文件保存为命令行中指定的文件名的文件中
-O:使用URL中默认的文件名保存文件到本地
# 将文件下载到本地并命名为test.html
$ curl -o test.html "http://apistore.baidu.com/apiworks/servicedetail/478.html"
# 将文件保存到本地并命名为478.html
$ curl -O "http://apistore.baidu.com/apiworks/servicedetail/478.html"
curl -L 重定向
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 默认情况下CURL不会发送HTTP Location headers(重定向).当一个被请求页面移动到另一个站点时,会发送一个HTTP Loaction header作为请求,然后将请求重定向到新的地址上。
# 例如:浏览器访问京东以前的域名www.360buy.com,会自动将地址重定向到www.jd.com上。通过curl命令就会出现如下信息
$ curl "www.360buy.com"
<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">
<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<h1>301 Moved Permanently</h1>
<p>The requested resource has been assigned a new permanent URI.</p>
<hr/>Server: JDWS</body>
</html>
# 这时可以通过使用-L选项进行强制重定向,让curl使用地址重定向,此时就会返回京东首页的信息
$ curl -L "www.360buy.com"
curl -O 从FTP服务器下载文件
1
2
3
4
5
6
7
8
9
# curl同样支持FTP下载,若在url中指定的是某个文件路径而非具体的某个要下载的文件名, curl则会列出该目录下的所有文件名而并非下载该目录下的所有文件
# 列出public_html下的所有文件夹和文件
$ curl -u user:pass -O "ftp://ftp_server/html/"
# 下载index.css文件
$ curl -u user:pass -O "ftp://ftp_server/css/index.css"
# 从FTP服务器下载文件的另一种写法
$ curl "ftp://user:pass@ftp_server:port/path/file"
curl -T 上传文件到FTP服务器
1
2
3
4
5
6
# 通过 -T 选项可将指定的本地文件上传到FTP服务器上
# 将myfile.txt文件上传到服务器
$ curl -u user:pass -T myfile.txt "ftp://ftp.testserver.com:port/path/"
# 同时上传多个文件
$ curl -u user:pass -T "{file1,file2}" "ftp://ftp.testserver.com"
curl -u 授权(没有测试过)
1
2
3
4
5
# 在访问需要授权的页面时,可通过-u选项提供用户名和密码进行授权
$ curl -u username:password URL
# 通常的做法是在命令行只输入用户名,之后会提示输入密码,这样可以保证在查看历史记录时不会将密码泄露
$ curl -u username URL
curl -x 设置代理
1
2
3
# -x 选项可以为CURL添加代理功能
# 指定代理主机和端口
$ curl -x proxysever.com:8888 -U user:pass "http://google.co.jp"

参考文章: