在前面的篇章里面,我们已经介绍了常见的各种shell命令操作。然而,在实际工作中,我们经常会遇到多种任务需要在一定时间内都解决掉。如果这些任务是相互独立的,即不存在先后执行关系,那么我们完全可以让它们同时处理,这样可以大大缩短任务的执行时间,提高我们的工作效率。在其他高级语言中,比如C++或者java,我们通常会采用多线程或者多进程来处理。同样,利用shell脚本我们也能实现多任务并发处理。这一节将介绍如何利用shell脚本进行多任务并发处理。
1.基本原理
关于如何创建多个进程同时运行,在前面的章节我们已有介绍,可以通过协程或者&操作实现。在这里主要讲如何进行控制,即如何按照预期目标让这些多进程有序进行。
要控制创建的进程按照预期目标进行操作,必然涉及到进程间的通信。关于进程间通信的技术有很多,比如临界区、管道、文件、信号、信号量、套接字等等。而对于多进程并发操作,我们将采用管道技术进行控制。
在类Unix操作系统中,管道是一个由标准输入输出链接起来的进程集合,因此,每一个进程的输出将直接作为下一个进程的输入。本质上讲,管道是一种特殊的文件。
管道有一个特点,如果管道中没有数据,那么取管道数据的操作就会滞留,直到管道内进入数据,然后读出后才会终止这一操作;同理,写入管道的操作如果没有读取管道的操作,这一动作就会滞留。
然而与之前介绍的管道命令“|”不同的是,这个管道需要创建者进行命名。为了区分它们,我们把前一种管道称为匿名管道,后一种管道称为命名管道。接下来,我们将详细比较这种管道的异同,以及为什么在多进程并发中需要采用命名管道。
1.1 匿名管道
在类Unix操作系统的命令行中,匿名管道使用ASCII中垂直线“|”作为匿名管道符,匿名管道的两端是两个普通的、匿名的、打开的文件描述符:一个只读端和一个只写端,这就让其它进程无法连接到该匿名管道。
例如:
cat file | cut -b 3
为了执行上面的指令,Shell创建了两个进程来分别执行cat和cut。值得注意的是,两个进程都连接到了管道上,这样写入进程cat就将其标准输出(文件描述符为fd 1)连接到了管道的写入端,读取进程cut就将其标准输入(文件描述符为fd 0)连接到了管道的读入端。实际上,这两个进程并不知道管道的存在,它们只是从标准文件描述符中读取数据和写入数据。shell必须要完成相关的工作。
1.2命名管道
命名管道也称FIFO,是First In First Out的缩写。从语义上来讲,FIFO其实与匿名管道类似,但值得注意:在文件系统中,FIFO拥有名称,并且是以设备特俗文件的形式存在的;
任何进程都可以通过FIFO共享数据;
除非FIFO两端同时有读与写的进程,否则FIFO的数据流通将会阻塞;
匿名管道是由shell自动创建的,存在于内核中;而FIFO则是由程序创建的(比如mkfifo命令),存在于文件系统中;
匿名管道是单向的字节流,而FIFO则是双向的字节流;
1.3多进程并发控制
在有了前面的基础知识之后,我们来讲如何使用命名管道进行多进程并发控制。
命名管道,从它的缩写我们可以看出它具有队列的性质,实际亦然。使用命名管道进行多进程并发控制的基本步骤为:创建一个命令管道
将命名管道绑定到一个特定的文件描述符
设置命名管道的大小。注意命名管道的大小,是通过写入记录的行数来决定的
依次读取命名管道,来创建运行新进程,执行多任务
四个步骤对应四个关键点,因此学习的时候需要牢牢记住。
2.具体实现
由于oracle数据库是目前最好的商用关系型数据库,在实际的工作中我们会经常用到。在关于数据库的操作,我们经常要做的就是数据的导入导出。这里以此为例,介绍如何使用shell多任务并发处理脚本进行oracle数据库的数据导入。
#! /bin/bash
source /etc/profile;
tempfifo=$$.fifo # $$表示当前执行文件的PID
cnt=$1 # 进程数目
data_path=$2 # 数据文件路径
ctl_path=$3 #控制文件路径
db_user=$4 #数据库用户名
db_passwd=$5 #数据库密码
#捕获Ctrl+C中断命令,则关闭文件描述符1000的读写,并正常退出
trap "exec 10>&-;exec 10
#创建一个管道文件,并与文件描述符10绑定
mkfifo $tempfifo
exec 10<>$tempfifo
rm -rf $tempfifo
#对文件描述符10进行写入操作。通过循环写入cnt个空行,这个cnt就是我们要定义的后台并发的线程数。
for ((i=1; i<=$cnt; i++))
do
echo >&10
done
for file in ` ls $data_path `
do
#读取命名管道内容,并创建进程在后台运行
read -u10
{
#导入数据文件到oracle数据库,具体不详细写明,大家可以根据需要结合后面的内容自己实现
obcp $file $db_user $db_passwd $data_path $ctl_path
#写入空行,保持命名管道的容量不变,不然会阻塞
echo >&10
} &
done
#等待子进程都退出,然后关闭文件描述符10再退出
wait
echo "done"
exec 10>&-
exec 10
exit 0
3.oracle数据库的建表与导数相关操作
3.1 oracle数据库的连接
sqlplus user/passwd@ip:port/dbname
3.2 清空已有的表
例如:删除当前用户下的所有表的存储过程
create or replace PROCEDURE pro_dropalltables IS
BEGIN
declare cursor cur_tables is (select table_name from USER_TABLES);
begin
for v_table in cur_tables
loop
DBMS_OUTPUT.PUT_LINE('grant all on ' || v_table.table_name || ' to public ');
execute immediate 'drop table ' || v_table.table_name || ' cascade constraints purge ';
end loop;
end;
END pro_dropalltables ;
3.3 建立建表辅助存储过程
例如:删除表dropifexits.sql
create or replace PROCEDURE pro_dropifexits(p_table in varchar2)
AS
BEGIN
select count(*) into v_count from from USER_TABLES where table_name=upper(p_table ))
IF v_count>0 then
DBMS_OUTPUT.PUT_LINE('drop table' || p_table);
execute immediate 'drop table' || p_table ;
end IF;
END pro_dropifexits;
3.4 建表
exec pro_dropifexits('xxx');
create table tab_xxx(
xx char(10),
...
...
);
partition by list(xxx)
(
partition partiton_xx values('xxxxxx');
);
grant all on tab_xxx to dbuser;
3.5 添加、删除分区
例如:添加分区
create or replace PROCEDURE pro_addpartition(p_centre_id in varchar2,tablespace_var in varchar2,partition_var in varchar2)
AS
BEGIN
declare cursor cur_tables is select table_name from USER_PART_TABLES where table_name in (select table_name from USER_TABLES);
begin
for v_table in cur_tables
loop
DBMS_OUTPUT.PUT_LINE('grant all on ' || v_table.table_name || ' to public ');
execute immediate 'alter table' || v_table.table_name || ' add partition' || partition_var || 'values( ' || p_centre_id || ') tablespace' || tablespace_var ;
end loop;
end;
END pro_addpartition ;
例如:删除分区
create or replace PROCEDURE pro_addpartition(p_centre_id in varchar2,tablespace_var in varchar2,partition_var in varchar2) is v_count number(10);
BEGIN
declare cursor cur_tables is select table_name from USER_PART_TABLES where table_name in (select table_name from USER_TABLES);
begin
for v_table in cur_tables
loop
select count(*) into v_count from user_tab_partitions where TABLE_NAME=v_table.table_name in (select table_name from USER_TABLES)
IF v_count>0 then
DBMS_OUTPUT.PUT_LINE('grant all on ' || v_table.table_name || ' to public ');
execute immediate 'alter table' || v_table.table_name || ' drop partition' || partition_var ;
end IF;
end loop;
end;
END pro_addpartition ;
3.6 授权
例如:PRO_GRANTSCRIPT .sql
create or replace PROCEDURE PRO_GRANTSCRIPT IS
BEGIN
declare cursor cur_tables is (select table_name from USER_TABLES);
begin
for v_table in cur_tables
loop
DBMS_OUTPUT.PUT_LINE('grant all on ' || v_table.table_name || ' to public ');
execute immediate 'grant all on ' || v_table.table_name || ' to public ';
end loop;
end;
END PRO_GRANTSCRIPT ;
3.7 导数
数据格式:以"|!"作为属性分隔符,"!@!"作为元组分隔符
sqlldr命令格式
sqlldr infile='xxx' ctrl='/path/xx.ctrl' bad='/path/xx.bad' log='/path/xx.log' errors=1000
ctrl格式:
LOAD DATA
INFILE '/home/wan/xx.txt' "str X'0A214021'"
INTO TABLE tab_xx
insert(append)
fields terminated by '|!'
trailing nullcols
(
xx char(10),
...
...
)
另一种解决方式是将sqlldr写成一个执行命令,然后将ctrl文件批量化,最后就可以批量执行导数功能。
3.8 exp和imp导出、导入数据库备份
exp信息:
exp user/passwd@ip:port file=xxx/xxx.dmp log=xxx/xxx.log
imp信息:
imp user/passwd@ip:port file=xxx/xxx.dmp log=xxx/xxx.log full=y ignore=y
3.9 使用spool将执行结果写入文件
例如:查找所有的表名
set heading off;
set feedback off;
set trimspool on;
set colsep '|';
set wrap on;
spool /home/wan/file/test.txt
select table_name from USER_TABLES;
spool off;
如果觉得《shell mysql并发_shell脚本中的多进程并发处理》对你有帮助,请点赞、收藏,并留下你的观点哦!