1、man
荔波ssl适用于网站、小程序/APP、API接口等需要进行数据传输应用场景,ssl证书未来市场广阔!成为创新互联的ssl证书销售渠道,可以享受市场价格4-6折优惠!如果有意向欢迎电话联系或者加微信:13518219792(备注:SSL证书合作)期待与您的合作!
manual 是类 UNIX 系统的标准手册工具,共 9 卷,用 man man 可以看到 9 卷分别是什么内容:
如果只有一个含义,直接查询即可,比如 man ls ,但有很多是一词多义的: chmod, chown, chmod, stat, exit, info, socket, flock, open, write, printf, passwd 等等。
man 1 printf 表示 shell 命令,
man 3 printf 表示 C 语言标准库函数,
可以用 -a 选项显示全部,比如 man -a printf ,按 q 退出之后,可以按回车键表示继续看下一卷,或按 Ctrl-C 直接退出。
2、#!/bin/bash
#!/bin/bash 放在 shell 脚本开头,是指此脚本使用 /bin/bash 来解释执行。
#!/usr/bin/python 也是类似的, 表示用 /usr/bin/python 来解释执行 python 脚本,加了这行就可以用 ./xx.py 的方式来运行了。
#!/usr/bin/env python 会去环境变量路径中寻找 python 目录。
3、shell 运行方式
(1) bash 1.sh
创建子进程运行 1.sh,需要有 r 权限。
(2) ./1.sh
创建子进程运行 1.sh,需要有 r 和 x 权限。
(3) source 1.sh
当前进程运行 1.sh,需要有 r 权限,
(4) . 1.sh
点命令是 source 命令的另一种写法。
如何证明是在当前进程运行,还是创建子进程运行呢,下面用 2 种简单的方法:
(1) 创建一个子目录,然后用 cd 命令
使用 bash 执行结束后,可以看左边的提示符,当前的工作目录没有改变, cd 命令只对子进程产生影响:
使用 source 执行结束后,当前目录变成了 child 目录,因为 cd 命令对当前进程产生了影响:
(2) 变量赋值
bash 是创建子进程来执行脚本,所以子进程的变量,在父进不能访问;
而 source 在当前进程执行,所以脚本中对变量的赋值,当前进程能访问:
4、内部命令,外部命令
通过 type 命令可以查询某个命令是内部命令、外部命令,还是别名( alias ):
如果用 bash shell,可以通过 help 命令列出所有内部命令列表。
区别:
(1)内部命令不创建子进程,对当前进程有影响,执行速度快;
外部命令不在shell中,保存在一个独立的文件中,会创建新的进程来执行。
(2)内部命令查询帮助信息用:help 内部命令,比如 help cd ,
外部命令查询帮助信息用:外部命令 --help,比如 ls --help 。
当然除了 man 、 help 之外, info 也可以用于查询帮助信息。
5、重定向
(1) 输入重定向
(2) 输出重定向
① 先清空该文件,再追加内容。
② 文件的内容不变,在文件末尾追加内容。
③ 2 错误重定向
④ 表示无论是正确还是错误,都输出重定向。
(3) 输入、输出重定向的组合使用
将开始标记 和结束标记之间的内容作为 cat 的输入, cat 的输出重定向到文件 1.txt:
这里的 EOF 只是开始和结尾的 tag,也可以换成其他的,比如ABCD:
6、变量
(1) 赋值, = 的两边不能有空格,否则被解释为命令:
(2) 算数运算用 let ,否则默认是字符串
(3) 把命令赋值给变量,意义不大:
(4) 把命令的结果赋值给变量:
(5) 作用范围
变量只在当前 shell 生效,子shell、父shell 都不能共用。
使用 export 命令之后,子shell 可以获取 父shell 的变量。其他终端不可以。
(6)变量不用了之后,可以删除: unset 变量名。
7、环境变量
(1) env 命令可以列出所有环境变量,一般是大写。
PATH 指定命令搜索路径,
PS1 可以使当前终端显示更友好,
UID 用户ID,
USER 用户名。
PWD 当前路径,
LD_LIBRARY_PATH 、 LD_PRELOAD 程序运行时,搜索动态库路径。
(2) set 命令可以列出系统中已经存在的 shell 变量,比 env 命令列出的变量更多。
(3) 预定义变量
$$ :当前进程的PID号
$0 :当前程序或脚本的名称
$? :用于返回上一条命令是否成功执行。如果成功执行,将返回数字0,否则返回非零数字(通常情况下都返回数字1)。
$* 或 $@ :保存传递给脚本或进程的所有参数
$# :脚本参数的个数,(类似C语言的 argc)
$1 、 $2 、 ${10} 等等 :位置变量,从命令行给脚本传参(类似C语言的argv[]),还有函数传参时会使用。
(4) 环境变量的配置文件
/etc/profile 是所有用户共用的。
~/.bashrc 是某一个用户特有的。
如果存在重复定义的变量, /etc/profile 先执行,会被后执行的 ~/.bashrc 覆盖。万一有变量被重复定义了,执行顺序很重要,可以在这 2 个文件中添加 echo 输出重定向到文件,来判断谁先被执行。
另外:
su root 是 non-login shell,不会加载所有的配置文件,不建议使用;
su - root 是 login shell,会加载所有配置文件,推荐使用这种方式。
8、数组
定义数组用小括号,例如: a=(1 2) ,
访问所有元素 ${a[@]} ,
数组元素的个数 ${#a[@]} ,
访问单个元素 ${a[0]} ,负号表示倒数第几个 ${a[-1]} 。
9、特殊字符
# 注释
; 分号用来分隔两条命令。
` 反引号 与 $() 都是用来执行命令的。
" 双引号(不完全引用)会对变量解释,
' 单引号(完全引用)不解释变量。
10、整数运算
默认会解释为字符串,所以需要用 expr ,注意空格:
除了 + 还有 - * / % 等等,注意转义字符:
不支持小数运算:
另外 length 可以计算字符串长度:
** 表示乘方, expr 没有乘方功能:
let 简写为双圆括号:
11、园括号 () 里面的赋值,不会影响外面。
是圆括号()创建了一个子进程吗,还是其他原因呢?
可以做一个小实验,先打开 2 个终端:
先查看有几个 bash 进程,可以看到是 5 个:
把圆括号睡眠一下,趁这个时间再次查看 bash 数量,比刚才多了 1 个,等 sleep 结束之后再看 bash 数量又恢复 5个,说明圆括号()确实创建了子进程:
另外也可以再通过 cd 命令来看,圆括号中改变了工作目录,并不影响父进程:
12、 test 命令
test 命令是通过设置 $? 的值, 来让我们可以判断是否正常退出,或者条件是否为真,0 为 真,非 0 为假。
test 通常有三类测试:字符串、数字、文件,可以通过 man 查询具体用法:
man test|grep INTEGER
man test|grep STRING
man test|grep FILE
通过 man test 可以看到 test EXPRESSION 和 [ EXPRESSION ] 两种写法,
有时为了程序美观,可以把 test 换成方括号 [ ] 的写法。
(1) 字符串
字符串是否相等,会区分大小写:
(2) 整数
[] 扩展为 [[]] ,可以支持更多运算符。
(3) 文件
文件是否存在:
目录存在:
文件或目录存在:
13、大括号
(1) 表示某个范围内的值,主要是给 for 循环使用的
(2) 减少重复书写
cp -v /etc/passwd{,.bak} 相当于 cp -v /etc/passwd /etc/passwd.bak
14、if
(1)if-then-fi
(2)if-then-else-fi
(3)if-then-elif-then-else-fi
15、case-esac
case 语句的例子在很多文件都有,比如 grep -n case ~/.bashrc
16、for
下面是 C 语言风格的写法:
17、while
用冒号表示条件为真,写一个死循环:
18、until
until 循环与 while 语法一致,只是条件为假才执行循环:
19、break 与 continue
20、字符串截取命令
basename 命令,可以用来删掉后缀名:
dirname 用于获取目录
其实它们不在乎这个目录是否真实存在,只是简单的处理一下字符串而已:
21、函数
函数定义的格式:
函数名(){ }
避免函数内部的变量影响到外面,使用 local 关键字限定作用域为该函数。
一、主要问题
对于简单的局域网环境下开发VFP程序,主要须注意的是数据共享冲突的问题。
二、解决办法
在明确网络设计的重点——如何解决两个或两个以上用户同时修改数据时所产生的冲突——后,我们来讨论解决这个问题的方法。
基本方法有三种:
1、对数据独占访问;
2、对数据共享访问;
3、缓冲数据访问;
其中1,2两点在庄稼的中级教程第十三课网络程序设计初步中有这方面的介绍,为了方便大家整体阅读,我在这里再多罗嗦两句。
1)独占方式
以独占方式打开的表,其他用户不能对该文件读写。
代码:
set exclusive on 或 use XXX exclusive
use XXX
只能在独占方式下执行的命令有index,reindex,pack,zap,alter table,insert,
modi stru等
2)共享方式
以共享方式打开的表,多用户可同时访问它。
代码:
set exclusive off 或 use XXX shared
use XXX
对于共享的数据表,在对其进行添加或更新时,就涉及到锁定与解锁的问题。
[2-1]锁定数据
锁定数据一般有自动锁定和人工锁定两种。
自动锁定就是一些vfp命令在执行之前都会自动锁定一个记录或一个表,如果成功锁定后才继续执行该命令,执行完后解锁。(vfp命令的自动锁定范围可参见相关书籍)
人工锁定是通过使用锁定函数人工锁定一个记录或一个表。可能用到的函数有:
Rlock() 锁定记录(rlock()等同于lock())
Flock() 锁定表
set reprocess to [尝试加锁次数]
set multilock on 锁定多条记录的系统设置
ISRlocked()/Isflocked() 测试该记录是否被锁定
代码:
set exclusive off 共享方式
set reprocess to 1 加锁一次
use XXX 打开共享表
IF Flock() 锁定表并判断是否成功
如果不想锁记录,只想测试记录是否加锁,则使用isrlocked()
replace all XX with XX 锁定成功执行替换
unlock 解锁
else
wait window "File in use by another user." nowait
endif
[2-2]数据解锁
数据解锁相对于加锁亦有两种解锁方式。
对于自动加锁的记录,只需移动记录指针就能解锁。
对于人工加锁的记录,必须使用下面的解锁命令:
unlock,unlock all
end transaction,tableupdate()
另外在使用set multilocks off或flock()命令建立新的锁定前,自动解除当前锁定还有比较彻底的命令:clear all,close all,quit.怎么样?彻底吧!:)
[2-3]我还想说的一点
设计表单时,使用私有数据工作期,原因很长,就不说了。
3)缓冲方式
缓冲方式包括两种手段:共享数据使用缓冲及事务处理的概念.
一、什么是文件锁定
对于锁这个字,大家一定不会陌生,因为我们生活中就存在着大量的锁,它们各个方面发挥着它的作用,现在世界中的锁的功能都可归结为一句话,就是阻止某些人做某些事,例如,门锁就是阻止除了屋主之外的人进入这个房子,你进入不到这个房子,也就不能使用房子里面的东西。
而因为程序经常需要共享数据,而这通常又是通过文件来实现的,试想一个情况,A进程正在对一个文件进行写操作,而另一个程序B需要对同一个文件进行读操作,并以读取到的数据作为自己程序运行时所需要的数据,这会发生什么情况呢?进程B可能会读到错乱的数据,因为它并不知道另一个进程A正在改写这个文件中的数据。
为了解决类似的问题,就出现了文件锁定,简单点来说,这是文件的一种安全的更新方式,当一个程序正在对文件进行写操作时,文件就会进入一种暂时状态,在这个状态下,如果另一个程序尝试读这个文件,它就会自动停下来等待这个状态结束。Linux系统提供了很多特性来实现文件锁定,其中最简单的方法就是以原子操作的方式创建锁文件。
用回之前的例子就是,文件锁就是当文件在写的时候,阻止其他的需要写或者要读文件的进程来操作这个文件。
二、创建锁文件
创建一个锁文件是非常简单的,我们可以使用open系统调用来创建一个锁文件,在调用open时oflags参数要增加参数O_CREAT和O_EXCL标志,如file_desc = open("/tmp/LCK.test", O_RDWR|O_CREAT|O_EXCL, 0444);就可以创建一个锁文件/tmp/LCK.test。O_CREAT|O_EXCL,可以确保调用者可以创建出文件,使用这个模式可以防止两个程序同时创建同一个文件,如果文件(/tmp/LCK.test)已经存在,则open调用就会失败,返回-1。
如果一个程序在它执行时,只需要独占某个资源一段很短的时间,这个时间段(或代码区)通常被叫做临界区,我们需要在进入临界区之前使用open系统调用创建锁文件,然后在退出临界区时用unlink系统调用删除这个锁文件。
注意:锁文件只是充当一个指示器的角色,程序间需要通过相互协作来使用它们,也就是说锁文件只是建议锁,而不是强制锁,并不会真正阻止你读写文件中的数据。
可以看看下面的例子:源文件文件名为filelock1.c,代码如下:
#include unistd.h #include stdlib.h #include stdio.h #include fcntl.h #include errno.h int main() { const char *lock_file = "/tmp/LCK.test1"; int n_fd = -1; int n_tries = 10; while(n_tries--) { //创建锁文件 n_fd = open(lock_file, O_RDWR|O_CREAT|O_EXCL, 0444); if(n_fd == -1) { //创建失败 printf("%d - Lock already present ", getpid()); sleep(2); } else { //创建成功 printf("%d - I have exclusive access ", getpid()); sleep(1); close(n_fd); //删除锁文件,释放锁 unlink(lock_file); sleep(2); } } return 0; }
同时运行同一个程序的两个实例,运行结果为:

从运行的结果可以看出两个程序交叉地对对文件进行锁定,但是真实的操作却是,每次调用open函数去检查/tmp/LCK.test1这个文件是否存在,如果存在open调用就失败,显示有进程已经把这个文件锁定了,如果这个文件不存在,就创建这个文件,并显示许可信息。但是这种做法有一定的缺憾,我们可以看到文件/tmp/LCK.test1被创建了很多次,也被unlink删除了很多次,也就是说我们不能使用已经事先有数据的文件作为这种锁文件,因为如果文件已经存在,则open调用总是失败。
给我的感觉是,这更像是一种对进程工作的协调性安排,更像是二进制信号量的作用,文件存在为0,不存在为1,而不是真正的文件锁定。
三、区域锁定
我们还有一个问题,就是如果同一个文件有多个进程需要对它进行读写,而一个文件同一时间只能被一个进程进行写操作,但是多个进程读写的区域互不相关,如果总是要等一个进程写完其他的进程才能对其进行读写,效率又太低,那么是否可以让多个进程同时对文件进行读写以提高数据读写的效率呢?
为了解决上面提到的问题,和出现在第二点中的问题,即不能把文件锁定到指定的已存在的数据文件上的问题,我们提出了一种新的解决方案,就是区域锁定。
简单点来说,区域锁定就是,文件中的某个部分被锁定了,但其他程序可以访问这个文件中的其他部分。
然而,区域锁定的创建和使用都比上面说的文件锁定复杂很多。
1、创建区域锁定
在Linux上为实现这一功能,我们可以使用fcntl系统调用和lockf调用,但是下面以fcntl系统调用来讲解区域锁定的创建。
fctnl的函数原理为:
int fctnl(int fildes, int command, ...);
它对一个打开的文件描述进行操作,并能根据command参数的设置完成不同的任务,它有三个可选的任务:F_GETLK,F_SETLK,F_SETLKW,至于这三个参数的意义下面再详述。而当使用这些命令时,fcntl的第三个参数必须是一个指向flock结构的指针,所以在实际应用中,fctnl的函数原型一般为:int fctnl(int fildes, int command, struct flock *flock_st);
2、flock结构
准确来说,flock结构依赖具体的实现,但是它至少包括下面的成员:
short l_type;文件锁的类型,对应于F_RDLCK(读锁,也叫共享锁),F_UNLCK(解锁,也叫清除锁),F_WRLCK(写锁,也叫独占锁)中的一个。
short l_whence;从文件的哪个相对位置开始计算,对应于SEEK_SET(文件头),SEEK_CUR(当前位置),SEEK_END(文件尾)中的一个。
off_t l_start;从l_whence开始的第l_start个字节开始计算。
off_t l_len;锁定的区域的长度。
pid_t l_pid;用来记录参持有锁的进程。
成员l_whence、l_start和l_len定义了一个文件中的一个区域,即一个连续的字节集合,例如:
struct flock region;
region.l_whence = SEEK_SET;
region.l_start = 10;
region.l_len = 20;
则表示fcntl函数操作锁定的区域为文件头开始的第10到29个字节之间的这20个字节。
3、文件锁的类型
从上面的flock的成员l_type的取值我们可以知道,文件锁的类型主要有三种,这里对他们进行详细的解说。
F_RDLCK:
从它的名字我们就可以知道,它是一个读锁,也叫共享锁。许多不同的进程可以拥有文件同一(或重叠)区域上的读(共享)锁。而且只要任一进程拥有一把读(共享)锁,那么就没有进程可以再获得该区域上的写(独占)锁。为了获得一把共享锁,文件必须以“读”或“读/写”方式打开。
简单点来说就是,当一个进程在读文件中的数据时,文件中的数据不能被改变或改写,这是为了防止数据被改变而使读数据的程序读取到错乱的数据,而文件中的同一个区域能被多个进程同时读取,这是容易理解的,因为读不会破坏数据,或者说读操作不会改变文件的数据。
F_WRLCK:
从它的名字,我们就可以知道,它是一个写锁,也叫独占锁。只有一个进程可以在文件中的任一特定区域拥有一把写(独占)锁。一旦一个进程拥有了这样一把锁,任何其他进程都无法在该区域上获得任何类型的锁。为了获得一把写(独占)锁,文件也必须以“读”或“读/写”方式打开。
简单点来说,就是一个文件同一区域(或重叠)区域进在同一时间,只能有一个进程能对其进行写操作,并且在写操作进行期间,其他的进程不能对该区域进行读取数据。这个要求是显然易见的,因为如果两个进程同时对一个文件进行写操作,就会使文件的内容错乱起来,而由于写时会改变文件中的数据,所以它也不允许其他进程对文件的数据进行读取和删除文件等操作。
F_UNLCK:
从它的名字就可以知道,它用于把一个锁定的区域解锁。
4、不同的command的意义
在前面说到fcntl函数的command参数时,说了三个命令选项,这里将对它们进行详细的解说。
F_GETLK命令,它用于获取fildes(fcntl的第一个参数)打开的文件的锁信息,它不会尝试去锁定文件,调用进程可以把自己想创建的锁类型信息传递给fcntl,函数调用就会返回将会阻止获取锁的任何信息,即它可以测试你想创建的锁是否能成功被创建。fcntl调用成功时,返回非-1,如果锁请求可以成功执行,flock结构将保持不变,如果锁请求被阻止,fcntl会用相关的信息覆盖flock结构。失败时返回-1。
所以,如果调用成功,调用程序则可以通过检查flock结构的内容来判断其是否被修改过,来检查锁请求能否被成功执行,而又因为l_pid的值会被设置成拥有锁的进程的标识符,所以大多数情况下,可以通过检查这个字段是否发生变化来判断flock结构是否被修改过。
使用F_GETLK的fcntl函数调用后会立即返回。
举个例子来说,例如,有一个flock结构的变量,flock_st,flock_st.l_pid = -1,文件的第10~29个字节已经存在一个读锁,文件的第40~49个字节中已经存在一个写锁,则调用fcntl时,如果用F_GETLK命令,来测试在第10~29个字节中是否可以创建一个读锁,因为这个锁可以被创建,所以,fcntl返回非-1,同时,flock结构的内容也不会改变,flock_st.l_pid = -1。而如果我们测试第40~49个字节中是否可以创建一个写锁时,由于这个区域已经存在一个写锁,测试失败,但是fcntl还是会返回非-1,只是flock结构会被这个区域相关的锁的信息覆盖了,flock_st.l_pid为拥有这个写锁的进程的进程标识符。
F_SETLK命令,这个命令试图对fildes指向的文件的某个区域加锁或解锁,它的功能根据flock结构的l_type的值而定。而对于这个命令来说,flock结构的l_pid字段是没有意义的。如果加锁成功,返回非-1,如果失败,则返回-1。使用F_SETLK的fcntl函数调用后会立即返回。
F_SETLKW命令,这个命令与前面的F_SETLK,命令作用相同,但不同的是,它在无法获取锁时,即测试不能加锁时,会一直等待直到可以被加锁为止。
5、例子
看了这么多的说明,可能你已经很乱了,就用下面的例子来整清你的思想吧。
源文件名为filelock2.c,用于创建数据文件,并将文件区域加锁,代码如下:
#include unistd.h #include stdlib.h #include stdio.h #include fcntl.h int main() { const char *test_file = "test_lock.txt"; int file_desc = -1; int byte_count = 0; char *byte_to_write = "A"; struct flock region_1; struct flock region_2; int res = 0; //打开一个文件描述符 file_desc = open(test_file, O_RDWR|O_CREAT, 0666); if(!file_desc) { fprintf(stderr, "Unable to open %s for read/write ", test_file); exit(EXIT_FAILURE); } //给文件添加100个‘A’字符的数据 for(byte_count = 0; byte_count 100; ++byte_count) { write(file_desc, byte_to_write, 1); } //在文件的第10~29字节设置读锁(共享锁) region_1.l_type = F_RDLCK; region_1.l_whence = SEEK_SET; region_1.l_start = 10; region_1.l_len = 20; //在文件的40~49字节设置写锁(独占锁) region_2.l_type = F_WRLCK; region_2.l_whence = SEEK_SET; region_2.l_start = 40; region_2.l_len = 10; printf("Process %d locking file ", getpid()); //锁定文件 res = fcntl(file_desc, F_SETLK, ®ion_1); if(res == -1) { fprintf(stderr, "Failed to lock region 1 "); } res = fcntl(file_desc, F_SETLK, ®ion_2); if(res == -1) { fprintf(stderr, "Failed to lock region 2 "); } //让程序休眠一分钟,用于测试 sleep(60); printf("Process %d closing file ", getpid()); close(file_desc); exit(EXIT_SUCCESS); }
下面的源文件filelock3.c用于测试上一个文件设置的锁,测试可否对两个区域都加上一个读锁,代码如下:
#include unistd.h #include stdlib.h #include stdio.h #include fcntl.h int main() { const char *test_file = "test_lock.txt"; int file_desc = -1; int byte_count = 0; char *byte_to_write = "A"; struct flock region_1; struct flock region_2; int res = 0; //打开数据文件 file_desc = open(test_file, O_RDWR|O_CREAT, 0666); if(!file_desc) { fprintf(stderr, "Unable to open %s for read/write ", test_file); exit(EXIT_FAILURE); } //设置区域1的锁类型 struct flock region_test1; region_test1.l_type = F_RDLCK; region_test1.l_whence = SEEK_SET; region_test1.l_start = 10; region_test1.l_len = 20; region_test1.l_pid = -1; //设置区域2的锁类型 struct flock region_test2; region_test2.l_type = F_RDLCK; region_test2.l_whence = SEEK_SET; region_test2.l_start = 40; region_test2.l_len = 10; region_test2.l_pid = -1; //
三、解空锁问题
如果我要给在本进程中没有加锁的区域解锁会发生什么事情呢?而如果这个区域中其他的进程有对其进行加锁又会发生什么情况呢?
如果一个进程实际并未对一个区域进行锁定,而调用解锁操作也会成功,但是它并不能解其他的进程加在同一区域上的锁。也可以说解锁请求最终的结果取决于这个进程在文件中设置的任何锁,没有加锁,但对其进行解锁得到的还是没有加锁的状态。