平铺合并
pc版本测试成功
1 | /** |
android 平台测试成功
1 | private static Bitmap overlapSmallToBitmap(Context context, int srcPng, int width, int height) { |
1 | /** |
1 | private static Bitmap overlapSmallToBitmap(Context context, int srcPng, int width, int height) { |
序号 | 函数 | 实例 |
---|---|---|
1 | date(timestring, modifier, modifier) | 以YYYY-MM-DD格式返回日期 |
2 | time(timestring, modifier, modifier) | 以HH:MM:SS格式返回时间 |
3 | datetime(timestring, modifier, modifier) | 以YYYY-MM-DD HH:MM:SS格式返回 |
4 | julianday(timestring, modifier, modifier) | 返回从格林尼治时间的公元前4714年11月24日正午的天数 |
5 | strftime(format, timestring, modifier, modifier) | 根据format指定格式返回时间 |
序号 | 时间字符串 | 实例 |
---|---|---|
1 | YYYY-MM-DD | 2010-12-30 |
2 | YYYY-MM-DD HH:MM | 2010-12-30 12:10 |
3 | YYYY-MM-DD HH:MM:SS.SSSS | 2010-12-30 12:10:04.100 |
4 | MM-DD-YYYY HH:MM | 30012-2010 12:10 |
5 | HH:MM | 12:10 |
6 | YYYY-MM-DDTHH:MM | 2010-12-30 12:10 |
7 | HH:MM:SS | 12:10:01 |
8 | YYYYMMDD HHMMSS | 20101230 121001 |
9 | now | 2017-1-13 |
注: 可以用T
作为时间和日期的分隔符.
时间字符串后边可跟着零个或多个修饰符, 这将改变上述五个函数返回的日期和时间. 修饰符从左到右使用.
可在sqlite中使用的修饰符, 如下:
替换 | 描述 |
---|---|
%d | 一月中的第几天, 01-31 |
%f | 带小数部分的秒, ss.sss |
%H | 小时, 00-23 |
%j | 一年中的第几天, 001-366 |
%J | 儒略日数, DDDD.DDDD |
%m | 月, 00-12 |
%M | 分, 00-59 |
%s | 从1970-01-01算起的秒数 |
%S | 秒, 00-59 |
%w | 一周中的第几天, 0-6,(0 is sunday, 周日) |
%W | 一年中的第几周 |
%Y | 年, YYYY |
%% | % |
1 | select date('now'); |
1 | select count(*) from company; |
alter table
命令不通过执行一个完整的转储和数据的重载来修改已有的表.alter table
语句来重命名表, 还可以在已有的表中添加额外的列.
sqlite中, 除了重命名表和添加列外, 不支持其他操作
1 | -- 重命名表 |
1 | alter table company rename to old_company; |
sqlite 中, 并没有truncate table命令,可以使用sqlte的delete命令删除表中已有的数据.
但是如果删除整个表,建议使用drop table命令.
1 | delete from table_name; |
1 | delete from company; |
1 | create [temp | temporary] view view_name as select column1, column2... |
1 | create view company company_view as select id, name, age from company; |
1 | drop view view_name; |
1 | -- 假设有age为25的记录 |
1 | select column_name [, column_name] from table1 [,table2] |
1 | insert into table_name[(column1, column2)] select [* | column1[,column2]] |
1 | update table set column_name = new_value |
1 | delete from table_name |
1 | -- 语法 |
TODO:
TODO:
以后追加
默认情况下, 列可以保存为NULL值; 如果不想, 则设置为NOT NULL;NULL和没有数据是不一样的, 代表未知的数据,
如下:
1 | create table employee( |
在insert时没有提供特定的值时, 提供一个默认值, 如下:
1 | create table employee( |
防止一个特定的列出现列个记录相同的值, 如下:
1 | create table employee( |
sqlite
中, 主键可以为NULL, 与其他数据库不同的地方.启用输入一条记录要检查值的条件. 如果为false, 则不能创建.
1 | create table company3( |
joins子句用于结合俩个或多个数据库中表的记录.
假设我们已经有表company, department, 往department中插入如下记录:1
2
3insert into department values(1, 'IT Billing', 1);
insert into department values(2, 'Engineering', 2);
insert into department values(3, 'Finance', 7);
把第一个表的每一行与第二个表的每一行进行匹配. 如果第一表有n行x列, 第二个表有m行y列, 则会产生n*m行
x+y列的结果表, 如下;
select * from company cross join department;
根据连接条件结合俩个表. 查询会把table1中的每一行与table2中的每一行进行比较, 如果满足连接条件,
就会进行合并. 内连接是默认连接, inner可以省略. 如下:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15select ... from table1 [inner] join table2 on condition_expression
-- 为了避免冗余, 并保持较短的措辞, 可以使用using表达式
select ... from table1 [inner] join table2 using(column1 ...)
-- 自然连接(natural join) 类似于join ... using , 只是它自会自动测试存在俩个表中每一列的值之间相等值.
select ... from table1 natural join table2;
--例子
select * from company inner join department on company.id=department.id;
-- 上面产生的效果类似于如下产生的效果:
select * from company natural join department;
-- using: id 俩个表都有, 所以正确, 连接后id列合并
select * from company inner join department using(id);
-- error: 由于name, dept不是俩个表共有的.
select * from company inner join department using(id, name, dept);
select * from company inner join department using(name, dept);
1 | select ... from table1 left outer join table2 on condition_expression |
UNION子句/运算符用于合并俩个或多个select语句的结果, 不返回任何重复的行.
为了使用UNION, 每个select被选择的列数必须是相同的, 相同数目的列表达式, 相同的数据类型,
并确保他们有相同的顺序, 但他们不必有相同的长度.1
2
3
4
5
6
7
8-- 语法
select column1 [,column2] from table1 [,table2] [where condition] union select column1 [,column2]
from table1 [,table2] [where condition]
-- 实例
select emp_id, name, dept from company inner join department on company.id = department.emp_id
union
select emp_id, name, dept from company left outer join department on company.id = department.emp_id;
同上, 但是会返回重复的行1
2
3
4
5
6
7
8
9-- 语法
select column1 [,column2] from table1 [,table2] [where condition]
union all
select column1 [,column2] from table1 [,table2] [where condition]
-- 实例
select emp_id, name, dept from company inner join department on company.id = department.emp_id
union all
select emp_id, name, dept from company left outer join department on company.id = department.emp_id;
sqlite的null是用来表示一个缺失值的项. 表中的一个NULL值是在字段中显示为空白的一个值.
带有null值的字段是一个不带有值的字段. null值和零值或包含空格的字段是不同的.
参照创建表
1 | select * from company where salary is not null; |
可以暂时把表或列重命名为另一个名字, 就是别名. 使用表别名是指在一个特定的sqlite语句中重命名表.
重命名是临时的改变, 数据库中实机的表是不会改变的. 列别名同表别名.
1 | -- 表别名 |
1 | -- 表别名 |
触发器是数据库的回调函数, 它会在指定的数据库事件发生时自动执行或调用. 以下是相关要点:
1 | -- 创建触发器 |
1 | -- 为company表的每一个记录保持审计试验 |
1 | -- 列出 |
只基于表的一个列上创建的索引.create index index_name on table_name(column_name);
唯一索引不允许任何重复的值插入到表中create unique index index_name on table_name(column_name);
基于表的俩个或多个列上创建的索引create index index_name on table_name(column1, column2);
创建表时, 由数据库服务器自动创建的索引. 索引自动创建为主键约束和唯一约束.
drop index index_name
1 | create index salary_index on company(salary); |
“indexed by index_name” 子句规定必须需要命名的索引来查找前面表中值. 如果不存在或不能用于查询,
语句失败. “not indexed” 子句规定当访问前面的表时, 没有使用索引. 然而, 即使指定了”not indexed”,
integer primary key仍然可以用于查找条目.
1 | select|delete|update column1, column2 indexed by (index_name) |
1 | -- 假设有表 |
select 检索记录
当数据插入时, 该字段的数据会优先采用亲缘类型作为该值的存储方式
或integer类型的数据, 如果失败,则以text存储;如'3000.00'的浮点数, 也会存储未real或integer;
1 | -- 例子 |
1 | drop table name; |
1 | -- 第一种插入 |
1 | -- 查看数据库中创建的表 |
+ - * / %
== = != <>
: 是否相等或不等的;> < >= <= !< !>
:大小比较;& | ~ >> <<
: 与c语言中类似
1 | select * from company where age >= 25 AND salary >= 65000; |
1 | -- 布尔表达式 |
用于修改表中的已有的记录, 可以使用带有where子句的update查询来更新选定行, 否则都会更新
1 | -- 更新一条记录 |
用于删除表中已有的记录, 可以使用带有where子句的delete查询来删除记录, 否则会全部删除
1 | -- 删除一条记录 |
用来匹配通配符指定模式的文本值. 如果搜索表达式与模式表达式匹配,返回真; 通配符如下:
1 | -- 通用式 |
用来匹配通配符指定模式的文本值. 与like不同的是, GLOB 是大小写敏感的; 通配符如下:
1 | -- 基本的 |
1 | select * from company limit 6; |
select column-list from table_name [where condition] [order by column1, column2, ] [asc(升)|desc(降)]
实例:1
2
3
4
5
6-- 按薪水升序排列
select * from company order by salary asc;
-- 按薪水降序排列
select * from company order by salary desc;
-- name, 薪水
select * from company order by name, salary asc;
select column-list from table where [conditions] group by column1,columnN order by column1,columnN
实例:1
2
3
4
5
6
7-- 如果想了解客户的工资总额
select into company values(9, 'teddy', 42, 'ggg', 90000);
-- 添加一条记录就可以看的更透彻
select * name,sum(salary) from company group by name;
-- 与order by一起使用
select name,sum(salary) from company group by name order by name desc;
1 | 允许指定条件来过滤将出现在最终结果中的分组结果. where子句在所选列上设置条件, 而having子句则在由 |
在select中的位置: select from where group by having order by, 必须在group by之后, order by之前.
1 | -- 显示名称计数小于2的所有记录 |
1 | distinct关键字和select一起使用, 来消除所有重复的记录, 并只获取唯一一次记录. 有可能出现一种情况, |
select distinct column1,column2,columnN from table_name where [condition]
1 | -- 返回的记录中有重复的 |
主要是在使用JNI的时候, 经常有些基本内容要去网上找, 有时比较繁琐, 故在此记录
基本数据类型:
Java类型 | Jni类型 | c++类型 | 描述 |
---|---|---|---|
boolean | jboolean | unsigned char | 无符号8位整数 |
byte | jbyte | signed char | 有符号8位整数 |
char | jchar | unsigned short | 无符号16位整数 |
short | jshort | signed short | 有符号16位整数 |
int | jint | signed int | 有符号32位整数 |
long | jlong | long long | 有符号64位整数 |
float | jfloat | float | 有符号32位浮点数 |
double | jdouble | double | 有符号64位浮点数 |
对象类型:
Java类型 | Jni类型 | 描述 |
---|---|---|
Object | jobject | 任意Java对象 |
Class | jclass | Class 对象,Java类 |
String | jstring | 字符串对象 |
类型对应的签名:
对于传统的JNI编程来说,JNI方法跟Java类方法的名称之间有一定的对应关系,要遵循一定的命名规则,如下:
_
进行分隔, 如: com_lms_jni_JniTest_
, 如果使用下划线的话则可能需要使用动态注册方法,如:getTestString返回值的参数 : jstring, jint…. 所以对于在Java类 com.dasea.jni.JniDemo中的一个方法:
1 | public native String addTail(int, String tail, int); |
其对应的jni层的native函数声明如下:
1 | jstring Java_com_dasea_jni_JniDemo_addTail(JNIEnv* env, jobject obj, jint, jstring, jint); |
base64是将二进制数据转换为64个可打印字符的转换方式.
将三字节转为四字节
, 如下:
如果不是三的整数倍, 则补0; 根据补的0的个数, 需要在结果字符串的最后面添加'='
实现如下: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
59const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
std::string base64_encode(uint8_t* in, uint32_t inLen) {
uint8_t arrayIn[3] = {0};
uint8_t arrayOut[4] = {0};
// 判断是否是
uint32_t round = (uint32_t)(inLen / 3);
uint32_t i = 0;
uint32_t j = 0;
std::vector<uint8_t> outRet;
while (i++ < round) {
arrayIn[0] = in[j++];
arrayIn[1] = in[j++];
arrayIn[2] = in[j++];
arrayOut[0] = ((arrayIn[0] & 0xfc) >> 2);
arrayOut[1] = ((arrayIn[0] & 0x03) << 4) + ((arrayIn[1] & 0xf0) >> 4);
arrayOut[2] = ((arrayIn[1] & 0x0f) << 2) + ((arrayIn[2] & 0xc0) >> 6);
arrayOut[3] = (arrayIn[2] & 0x3f);
outRet.push_back(base64_chars[arrayOut[0]]);
outRet.push_back(base64_chars[arrayOut[1]]);
outRet.push_back(base64_chars[arrayOut[2]]);
outRet.push_back(base64_chars[arrayOut[3]]);
}
// 如果不是三的倍数的话
// 处理余下的内容, 如果添加的话, 需要=号
round = (uint32_t)(inLen % 3);
if (1 == round) {
arrayIn[0] = in[inLen - 1];
arrayIn[1] = '\0';
arrayOut[0] = ((arrayIn[0] & 0xfc) >> 2);
arrayOut[1] = ((arrayIn[0] & 0x03) << 4) + ((arrayIn[1] & 0xf0) >> 4);
outRet.push_back(base64_chars[arrayOut[0]]);
outRet.push_back(base64_chars[arrayOut[1]]);
// outRet.push_back('=');
// outRet.push_back('=');
} else if (2 == round) {
arrayIn[0] = in[inLen - 2];
arrayIn[1] = in[inLen - 1];
arrayIn[2] = '\0';
arrayOut[0] = ((arrayIn[0] & 0xfc) >> 2);
arrayOut[1] = ((arrayIn[0] & 0x03) << 4) + ((arrayIn[1] & 0xf0) >> 4);
arrayOut[2] = ((arrayIn[1] & 0x0f) << 2) + ((arrayIn[2] & 0xc0) >> 6);
outRet.push_back(base64_chars[arrayOut[0]]);
outRet.push_back(base64_chars[arrayOut[1]]);
outRet.push_back(base64_chars[arrayOut[2]]);
// outRet.push_back('=');
}
// vec 转 string
std::string out(outRet.begin(), outRet.end());
return (out);
}
将四个字节转换为三个字节, 上面的逆过程.
实现如下: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
54uint8_t* base64_decode(std::string& in, uint32_t& outLen) {
uint8_t arrayIn[4] = {0};
uint8_t arrayOut[3] = {0};
uint32_t round = (uint32_t)(in.size() / 4);
uint32_t i = 0;
uint32_t j = 0;
std::vector<uint8_t> vecRet;
while (i++ < round) {
arrayIn[0] = static_cast<uint8_t>(base64_chars.find(in[j++]));
arrayIn[1] = static_cast<uint8_t>(base64_chars.find(in[j++]));
arrayIn[2] = static_cast<uint8_t>(base64_chars.find(in[j++]));
arrayIn[3] = static_cast<uint8_t>(base64_chars.find(in[j++]));
// 转换
arrayOut[0] = (arrayIn[0] << 2) + ((arrayIn[1] & 0x30) >> 4);
arrayOut[1] = ((arrayIn[1] & 0x0f) << 4) + ((arrayIn[2] & 0x3c) >> 2);
arrayOut[2] = ((arrayIn[2] & 0x03) << 6) + arrayIn[3];
// 三个数已知
vecRet.push_back(arrayOut[0]);
vecRet.push_back(arrayOut[1]);
vecRet.push_back(arrayOut[2]);
}
// 如果不是4的整数倍, 则其他的单独进行
round = (uint32_t)(in.size() % 4);
if (2 == round) {
// 可转换为一个字符
arrayIn[0] = static_cast<uint8_t>(base64_chars.find(in[in.size() - 2]));
arrayIn[1] = static_cast<uint8_t>(base64_chars.find(in[in.size() - 1]));
arrayOut[0] = (arrayIn[0] << 2) + ((arrayIn[1] & 0x30) >> 4);
vecRet.push_back(arrayOut[0]);
} else if (3 == round) {
// 可转换为2个字符
arrayIn[0] = static_cast<uint8_t>(base64_chars.find(in[in.size() - 3]));
arrayIn[1] = static_cast<uint8_t>(base64_chars.find(in[in.size() - 2]));
arrayIn[2] = static_cast<uint8_t>(base64_chars.find(in[in.size() - 1]));
arrayOut[0] = (arrayIn[0] << 2) + ((arrayIn[1] & 0x30) >> 4);
arrayOut[1] = ((arrayIn[1] & 0x0f) << 4) + ((arrayIn[2] & 0x3c) >> 2);
vecRet.push_back(arrayOut[0]);
vecRet.push_back(arrayOut[1]);
}
outLen = vecRet.size();
uint8_t* ret = new uint8_t[outLen];
for (i = 0; i < outLen; ++i) {
ret[i] = vecRet[i];
}
return (ret);
}
每个命令都有一个返回值(返回状态或者退出状态)。命令执行成功的返回值总是0
(零值),执行失败的命令,返回一个非0值(错误码)。错误码必须是一个1到255之间的整数。
在编写脚本时,另一个很有用的命令是exit
。这个命令被用来终止当前的执行,并把返回值交给shell。当exit
不带任何参数时,它会终止当前脚本的执行并返回在它之前最后一个执行的命令的返回值。
一个程序运行结束后,shell将其返回值赋值给$?
环境变量。因此$?
变量通常被用来检测一个脚本执行成功与否。
与使用exit
来结束一个脚本的执行类似,我们可以使用return
命令来结束一个函数的执行并将返回值返回给调用者。当然,也可以在函数内部用exit
,这 _不但_ 会中止函数的继续执行,_而且_ 会终止整个程序的执行。
脚本中可以包含 _注释_。注释是特殊的语句,会被shell
解释器忽略。它们以#
开头,到行尾结束。
1 | #!/bin/bash |
Bash中没有数据类型。变量只能包含数字或者由一个或多个字符组成的字符串
你可以创建三种变量:局部变量,环境变量以及作为 位置参数 的变量。
局部变量 是仅在某个脚本内部有效的变量。它们不能被其他的程序和脚本访问。
局部变量可以用=
声明(作为一种约定,变量名、=
、变量的值之间 不应该 有空格),
其值可以用$
访问到。举个例子:
1 | username="denysdovhan" # 声明变量 |
也可以用local
关键字声明属于某个函数的局部变量,这样声明的变量会在函数结束时消失。
1 | local local_var="I'm a local value" |
环境变量 是对当前shell会话内所有的程序或脚本都可见的变量.
创建它们跟创建局部变量类似,但使用的是export
关键字。
1 | export GLOBAL_VAR="I'm a global variable" |
bash中有 非常多 的环境变量。你会非常频繁地遇到它们,这里有一张速查表,记录了在实践中最常见的环境变量。
Variable | Description |
---|---|
$HOME |
当前用户的用户目录 |
$PATH |
用分号分隔的目录列表,shell会到这些目录中查找命令 |
$PWD |
当前工作目录 |
$RANDOM |
0到32767之间的整数 |
$UID |
数值类型,当前用户的用户ID |
$PS1 |
主要系统输入提示符 |
$PS2 |
次要系统输入提示符 |
位置参数 是在调用一个函数并传给它参数时创建的变量。下表列出了在函数中,位置参数变量和一些其它的特殊变量以及它们的意义。
Parameter | Description |
---|---|
$0 |
脚本名称 |
$1 … $9 |
第1个到第9个参数列表 |
${10} … ${N} |
第10个到N个参数列表 |
$* or $@ |
除了$0 外的所有位置参数 |
$# |
不包括$0 在内的位置参数的个数 |
$FUNCNAME |
函数名称(仅在函数内部有值) |
在下面的例子中,位置参数为:$0='./script.sh'
,$1='foo'
,$2='bar'
:
./script.sh foo bar
变量可以有 _默认_ 值。我们可以用如下语法来指定默认值:
1 | # 如果变量为空,赋给他们默认值 |
_扩展_ 发生在一行命令被分成一个个的 记号(tokens) 之后。换言之,扩展是一种执行数学运算的机制,还可以用来保存命令的执行结果,等等。
大括号扩展让生成任意的字符串成为可能。它跟 文件名扩展 很类似,举个例子:
1 | echo beg{i,a,u}n # begin began begun |
大括号扩展还可以用来创建一个可被循环迭代的区间。
1 | echo {0..5} # 0 1 2 3 4 5 |
命令置换允许我们对一个命令求值,并将其值置换到另一个命令或者变量赋值表达式中。当一个命令被1
2
3
4
5
6
7
```bash
now=`date +%T`
# or
now=$(date +%T)
echo $now # 19:08:26
在bash中,执行算数运算是非常方便的。算数表达式必须包在$(( ))
中。算数扩展的格式为:
1 | result=$(( ((10 + 5*3) - 7) / 2 )) |
在算数表达式中,使用变量无需带上$
前缀:
1 | x=4 |
单引号和双引号之间有很重要的区别。在双引号中,变量引用或者命令置换是会被展开的.
在单引号中是不会的。双引号中, 对于变量可以扩展, 但是不用于扩展通配符; 单引号都
展开,举个例子:
1 | echo "Your home: $HOME" # Your home: /Users/<username> |
当局部变量和环境变量包含空格时,它们在引号中的扩展要格外注意。随便举个例子,假如我们用echo
来输出用户的输入:
1 | INPUT="A string with strange whitespace." |
调用第一个echo
时给了它5个单独的参数 —— $INPUT被分成了单独的词,echo
在每个词之间打印了一个空格。第二种情况,调用echo
时只给了它一个参数(整个$INPUT的值,包括其中的空格)。
来看一个更严肃的例子:
1 | FILE="Favorite Things.txt" |
尽管这个问题可以通过把FILE重命名成Favorite-Things.txt
来解决,但是,假如这个值来自某个环境变量,来自一个位置参数,或者来自其它命令(find
, cat
, 等等)呢。因此,如果输入 可能 包含空格,务必要用引号把表达式包起来。
跟其它程序设计语言一样,bash中的数组变量给了你引用多个值的能力。在bash中,数组下标也是从0开始,也就是说,第一个元素的下标是0。
跟数组打交道时,要注意一个特殊的环境变量IFS
。IFS,全称 Input Field Separator,保存了数组中元素的分隔符。它的默认值是一个空格IFS=' '
。
在bash中,可以通过简单地给数组变量的某个下标赋值来创建一个数组:
1 | fruits[0]=Apple |
数组变量也可以通过复合赋值的方式来创建,比如:
1 | fruits=(Apple Pear Plum) |
单个数组元素的扩展跟普通变量的扩展类似:
1 | echo ${fruits[1]} # Pear |
整个数组可以通过把数字下标换成*
或@
来扩展:
1 | echo ${fruits[*]} # Apple Pear Plum |
上面两行有很重要(也很微妙)的区别,假设某数组元素中包含空格:
1 | fruits[0]=Apple |
为了将数组中每个元素单独一行输出,我们用内建的printf
命令:
1 | printf "+ %s\n" ${fruits[*]} |
为什么Desert
和fig
各占了一行?尝试用引号包起来:
1 | printf "+ %s\n" "${fruits[*]}" |
现在所有的元素都跑去了一行 —— 这不是我们想要的!为了解决这个痛点,${fruits[@]}
闪亮登场:
1 | printf "+ %s\n" "${fruits[@]}" |
在引号内,${fruits[@]}
将数组中的每个元素扩展为一个单独的参数;数组元素中的空格得以保留。
除此之外,可以通过 _切片_ 运算符来取出数组中的某一片元素:
1 | echo ${fruits[@]:0:2} # Apple Desert fig |
在上面的例子中,${fruits[@]}
扩展为整个数组,:0:2
取出了数组中从0开始,长度为2的元素。
向数组中添加元素也非常简单。复合赋值在这里显得格外有用。我们可以这样做:
1 | fruits=(Orange "${fruits[@]}" Banana Cherry) |
上面的例子中,${fruits[@]}
扩展为整个数组,并被置换到复合赋值语句中,接着,对数组fruits
的赋值覆盖了它原来的值。
用unset
命令来从数组中删除一个元素:
1 | unset fruits[0] |
Bash有很强大的工具来处理程序之间的协同工作。使用流,我们能将一个程序的输出发送到另一个程序或文件,因此,我们能方便地记录日志或做一些其它我们想做的事。
管道给了我们创建传送带的机会,控制程序的执行成为可能。
学习如何使用这些强大的、高级的工具是非常非常重要的。
Bash接收输入,并以字符序列或 字符流 的形式产生输出。这些流能被重定向到文件或另一个流中。
有三个文件描述符:
代码 | 描述符 | 描述 |
---|---|---|
0 |
stdin |
标准输入 |
1 |
stdout |
标准输出 |
2 |
stderr |
标准错误输出 |
重定向让我们可以控制一个命令的输入来自哪里,输出结果到什么地方。这些运算符在控制流的重定向时会被用到:
Operator | Description |
---|---|
> |
重定向输出 |
&> |
重定向输出和错误输出 |
&>> |
以附加的形式重定向输出和错误输出 |
< |
重定向输入 |
<< |
Here文档 语法 |
<<< |
Here字符串 |
以下是一些使用重定向的例子:
1 | # ls的结果将会被写到list.txt中 |
我们不仅能将流重定向到文件中,还能重定向到其它程序中。管道 允许我们把一个程序的输出当做另一个程序的输入。
在下面的例子中,command1
把它的输出发送给了command2
,然后输出被传递到command3
:
command1 | command2 | command3
这样的结构被称作 管道。
在实际操作中,这可以用来在多个程序间依次处理数据。在下面的例子中,ls -l
的输出被发送给了grep
,来打印出扩展名是.md
的文件,它的输出最终发送给了less
:
ls -l | grep .md$ | less
管道的返回值通常是管道中最后一个命令的返回值。shell会等到管道中所有的命令都结束后,才会返回一个值。如果你想让管道中任意一个命令失败后,管道就宣告失败,那么需要用下面的命令设置pipefail选项:
set -o pipefail
命令序列是由;
,&
,&&
或者||
运算符分隔的一个或多个管道序列。
如果一个命令以&
结尾,shell将会在一个子shell中异步执行这个命令。换句话说,这个命令将会在后台执行。
以;
分隔的命令将会依次执行:一个接着一个。shell会等待直到每个命令执行完。
1 | # command2 会在 command1 之后执行 |
以&&
和||
分隔的命令分别叫做 _与_ 和 _或_ 序列。
与序列 看起来是这样的:
1 | # 当且仅当command1执行成功(返回0值)时,command2才会执行 |
或序列 是下面这种形式:
1 | # 当且仅当command1执行失败(返回错误码)时,command2才会执行 |
_与_ 或 _或_ 序列的返回值是序列中最后一个执行的命令的返回值。
跟其它程序设计语言一样,Bash中的条件语句让我们可以决定一个操作是否被执行。结果取决于一个包在[[ ]]
里的表达式。
条件表达式可以包含&&
和||
运算符,分别对应 _与_ 和 _或_ 。除此之外还有很多有用的表达式。
共有两个不同的条件表达式:if
和case
。
由[[ ]]
(sh
中是[ ]
)包起来的表达式被称作 检测命令 或 基元。这些表达式帮助我们检测一个条件的结果。在下面的表里,为了兼容sh
,我们用的是[ ]
。这里可以找到有关bash中单双中括号区别的答案。
跟文件系统相关:
基元 | 含义 |
---|---|
[ -e FILE ] |
如果FILE 存在 (exists),为真 |
[ -f FILE ] |
如果FILE 存在且为一个普通文件(file),为真 |
[ -d FILE ] |
如果FILE 存在且为一个目录(directory),为真 |
[ -s FILE ] |
如果FILE 存在且非空(size 大于0),为真 |
[ -r FILE ] |
如果FILE 存在且有读权限(readable),为真 |
[ -w FILE ] |
如果FILE 存在且有写权限(writable),为真 |
[ -x FILE ] |
如果FILE 存在且有可执行权限(executable),为真 |
[ -L FILE ] |
如果FILE 存在且为一个符号链接(link),为真 |
[ FILE1 -nt FILE2 ] |
FILE1 比FILE2 新(newer than) |
[ FILE1 -ot FILE2 ] |
FILE1 比FILE2 旧(older than) |
跟字符串相关:
基元 | 含义 |
---|---|
[ -z STR ] |
STR 为空(长度为0,zero) |
[ -n STR ] |
STR 非空(长度非0,non-zero) |
[ STR1 == STR2 ] |
STR1 和STR2 相等 |
[ STR1 != STR2 ] |
STR1 和STR2 不等 |
算数二元运算符:
基元 | 含义 |
---|---|
[ ARG1 -eq ARG2 ] |
ARG1 和ARG2 相等(equal) |
[ ARG1 -ne ARG2 ] |
ARG1 和ARG2 不等(not equal) |
[ ARG1 -lt ARG2 ] |
ARG1 小于ARG2 (less than) |
[ ARG1 -le ARG2 ] |
ARG1 小于等于ARG2 (less than or equal) |
[ ARG1 -gt ARG2 ] |
ARG1 大于ARG2 (greater than) |
[ ARG1 -ge ARG2 ] |
ARG1 大于等于ARG2 (greater than or equal) |
条件语句可以跟 组合表达式 配合使用:
Operation | Effect |
---|---|
[ ! EXPR ] |
如果EXPR 为假,为真 |
[ (EXPR) ] |
返回EXPR 的值 |
[ EXPR1 -a EXPR2 ] |
逻辑 _与_, 如果EXPR1 和(and)EXPR2 都为真,为真 |
[ EXPR1 -o EXPR2 ] |
逻辑 _或_, 如果EXPR1 或(or)EXPR2 为真,为真 |
当然,还有很多有用的基元,在Bash的man页面能很容易找到它们。
if
if
在使用上跟其它语言相同。如果中括号里的表达式为真,那么then
和fi
之间的代码会被执行。fi
标志着条件代码块的结束。
1 | # 写成一行 |
同样,我们可以使用if..else
语句,例如:
1 | # 写成一行 |
有些时候,if..else
不能满足我们的要求。别忘了if..elif..else
,使用起来也很方便。
看下面的例子:
1 | if [[ `uname` == "Adam" ]]; then |
case
如果你需要面对很多情况,分别要采取不同的措施,那么使用case
会比嵌套的if
更有用。使用case
来解决复杂的条件判断,看起来像下面这样:
1 | case "$extension" in |
在每次循环的过程中,arg
依次被赋值为从elem1
到elemN
。这些值还可以是通配符或者大括号扩展。
当然,我们还可以把for
循环写在一行,但这要求do
之前要有一个分号,就像下面这样:
1 | for i in {1..5}; do echo $i; done |
还有,如果你觉得for..in..do
对你来说有点奇怪,那么你也可以像C语言那样使用for
,比如:
1 | for (( i = 0; i < 10; i++ )); do |
当我们想对一个目录下的所有文件做同样的操作时,for
就很方便了。举个例子,如果我们想把所有的.bash
文件移动到script
文件夹中,并给它们可执行权限,我们的脚本可以这样写:
1 | #!/bin/bash |
while
循环while
循环检测一个条件,只要这个条件为 _真_,就执行一段命令。被检测的条件跟if..then
中使用的基元并无二异。因此一个while
循环看起来会是这样:
1 | while [[ condition ]] |
跟for
循环一样,如果我们把do
和被检测的条件写到一行,那么必须要在do
之前加一个分号。
比如下面这个例子:
1 | #!/bin/bash |
until
循环until
循环跟while
循环正好相反。它跟while
一样也需要检测一个测试条件,但不同的是,只要该条件为 _假_ 就一直执行循环:
1 | until [[ condition ]]; do |
select
循环select
循环帮助我们组织一个用户菜单。它的语法几乎跟for
循环一致:
1 | select answer in elem1 elem2 ... elemN |
select
会打印elem1..elemN
以及它们的序列号到屏幕上,之后会提示用户输入。通常看到的是$?
(PS3
变量)。用户的选择结果会被保存到answer
中。如果answer
是一个在1..N
之间的数字,那么语句
会被执行,紧接着会进行下一次迭代 —— 如果不想这样的话我们可以使用break
语句。
一个可能的实例可能会是这样:
1 | #!/bin/bash |
这个例子,先询问用户他想使用什么包管理器。接着,又询问了想安装什么包,最后执行安装操作。
运行这个脚本,会得到如下输出:
1 | $ ./my_script |
我们会遇到想提前结束一个循环或跳过某次循环执行的情况。这些可以使用shell内建的break
和continue
语句来实现。它们可以在任何循环中使用。
break
语句用来提前结束当前循环。我们之前已经见过它了。
continue
语句用来跳过某次迭代。我们可以这么来用它:
1 | for (( i = 0; i < 10; i++ )); do |
运行上面的例子,会打印出所有0到9之间的奇数。
在脚本中,我们可以定义并调用函数。跟其它程序设计语言类似,函数是一个代码块,但有所不同。
bash中,函数是一个命令序列,这个命令序列组织在某个名字下面,即 函数名 。调用函数跟其它语言一样,写下函数名字,函数就会被 _调用_ 。
我们可以这样声明函数:
1 | my_func () { |
我们必须在调用前声明函数。
函数可以接收参数并返回结果 —— 返回值。参数,在函数内部,跟非交互式下的脚本参数处理方式相同 —— 使用位置参数。返回值可以使用return
命令 _返回_ 。
下面这个函数接收一个名字参数,返回0
,表示成功执行。
1 | # 带参数的函数 |
我们之前已经介绍过返回值。不带任何参数的return
会返回最后一个执行的命令的返回值。上面的例子,return 0
会返回一个成功表示执行的值,0
。
shell提供了用于debugging脚本的工具。如果我们想以debug模式运行某脚本,可以在其shebang中使用一个特殊的选项:
1 | #!/bin/bash options |
options是一些可以改变shell行为的选项。下表是一些可能对你有用的选项:
Short | Name | Description |
---|---|---|
-f |
noglob | 禁止文件名展开(globbing) |
-i |
interactive | 让脚本以 _交互_ 模式运行 |
-n |
noexec | 读取命令,但不执行(语法检查) |
-t |
— | 执行完第一条命令后退出 |
-v |
verbose | 在执行每条命令前,向stderr 输出该命令 |
-x |
xtrace | 在执行每条命令前,向stderr 输出该命令以及该命令的扩展参数 |
举个例子,如果我们在脚本中指定了-x
例如:
1 | #!/bin/bash -x |
这会向stdout
打印出变量的值和一些其它有用的信息:
1 | $ ./my_script |
有时我们需要debug脚本的一部分。这种情况下,使用set
命令会很方便。这个命令可以启用或禁用选项。使用-
启用选项,+
禁用选项:
1 | #!/bin/bash |
man bash
可以借助帮助系统man
来显示Bash的帮助信息。
1 | Sub SaveWorkSheetToBook() |
机器数
真值
==原码, 反码, 补码的基础概念和计算方法==
原码
反码
负数的反码是在其反码的基础上, 符号位不变, 其余位取反:如
如果一个反码表示负数, 人脑无法直接观看;
补码
对于正数都是一样的,所以忽略;
对于负数,不同, 为什么会存在呢?
人们继续探索将符号位参与运算, 并且只保留加法的方法:
使用补码, 不仅仅修复了0的符号以及存在俩个编码的问题, 而且还能够多表示一个最低数, 这就是为什么8位二进制, 使用原码或反码表示的范围是[-127, 127], 而使用补码表示的范围是[-128, 127];
参考网址:博客园已转载
1 | 如: |
1 | // 算法 |