本笔记用于个人陌生知识点查阅,内容并不详细完整,请见谅。
学习视频:黑马程序员-Linux系统编程_哔哩哔哩_bilibili
一、Linux基础命令
命令 | 可选项/路径 | 介绍 |
ls | -a -l -h -R | 列出目录的内容,a列出全部,l以列表形式,h以人类可读, -R递归展示 |
cd | [路径] | 切换工作目录 ./ 当前工作目录 ../上一级工作目录 ~/家目录 |
pwd | 无 | 查看当前工作目录 |
mkdir | -p [路径] | 创建新的目录(文件夹),-p表示自动创建不存在的父目录 |
touch | [路径] | 创建文件,路径的最后是文件名 例如:./home/wjsbetter/test.txt |
cat | [路径] | 查看文件 |
more | [路径] | 查看文件,支持空格翻页,q退出查看 |
cp | -r 参数1 参数2 | 将参数1的文件复制到参数2,-r表示复制文件夹 |
mv | 参数1 参数2 | 将参数1的文件移动到参数2 |
rm | -r -f 参数 | -r删除文件夹 ,-f强制删除,参数表示要删除的文件或文件夹路径 rm命令支持通配符 *,用来做模糊匹配。 |
su root | 无 | 跳转到root用户,需要输入密码 |
ctrl + d | 无 | 退出当前用户 |
which | 要查看的命令 | 查看命令程序文件的储存位置 |
find | 起始路径 -name "被查找的文件名" | 按名字查找文件,支持通配符 |
grep | -n 关键字 [路径] | n表示在结果中显示匹配的行的行号 |
| | 无 | 管道符,将左边的结果作为右边的输入 |
echo | 输出内容 | 在命令行输出指定内容,复杂内容可以用””包围 |
> | 无 | 将左边的内容覆盖写入到右侧指定文件中 |
>> | 无 | 将左边的内容追加写入到右侧指定文件中 |
head | [路径] | 查看文件头部内容 |
tree | 无 | 按树状结构显示目录结构 |
ln | -s 参数1 参数2 |
|
ps | a u x -e -f |
|
umask | 3个0-7的数字 | 设置权限掩码,是进程的概念,只能该某个进程的掩码 |
wc | -c -m -l -w | 做数量统计
|
echo $PATH :查看环境变量(PATH)的值,$的作用是引用PATH的值
Linux文件类型
除了下面这7种文件类型,其它的都叫未知文件:
普通文件: -
目录文件: d
字符设备文件:c
伪文件(不会真正占用无物理内存空间):
块设备文件: b
软连接: l
管道文件: p
套接字: s
二、常用vim编辑器命令(命令模式下)
功能 | 命令 |
显示行号 | set nu |
跳转到第 88 行 | 88G |
跳转到文件开头 | gg |
跳转到文件结尾 | G |
自动格式化程序(代码对齐) | gg=G |
删除/剪切整行 | dd |
删除指定N行 | Ndd 光标所在行为第一行 |
粘贴到光标前面一行 | P 大写 |
粘贴到光标后面一行 | p 小写 |
复制一行 | yy |
撤销操作 | u |
横向分屏 | sp ctrl+ww切换 |
竖向分屏 | vsp ctrl+ww切换 |
跳转至man手册 | K 或 nK |
三、makefile管理项目
src = $(wildcard ./src/*c)
obj = $(patsubst ./src/%.c, ./obj/%.o, $(src))
inc_path = ./inc
myArgs = -Wall -g
ALL:a.out
a.out:$(obj)
gcc $^ -o $@ $(myArgs)
$(obj):./obj/%.o:./src/%.c
gcc -c $< -o $@ $(myArgs) -I $(inc_path)
clean:
-rm -rf $(obj) a.out
.PHONY: clean ALL
四、文件IO
4.1.open
#include <unistd.h>
#include <fcntl.h>
int open(char *pathname, int flags)
参数:pathname: 欲打开的文件路径名
flags:文件打开方式
只读O_RDONLY 只写O_WRONLY 读写O_RDWR
创建O_CREAT 追加O_APPEND 清空O_TRUNC
判断文件是否存在O_EXCL 非阻塞O_NONBLOCK
返回值:成功: 打开文件所得到对应的 文件描述符(整数)
失败: -1, 设置errno
int open(char *pathname, int flags, mode_t mode)
参数: pathname: 欲打开的文件路径名
flags:文件打开方式
mode:参数3使用的前提, 参2指定了 O_CREAT。
取值8进制数,用来描述文件的 访问权限。 rwx 0664
创建文件最终权限 = mode & ~umask umask表示文件权限掩码
返回值: 成功: 打开文件所得到对应的 文件描述符(整数)
失败: -1, 设置errno
4.2.close
int close(int fd);
返回值:成功:返回 0 失败:返回 -1
4.3.read
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
参数:fd:文件描述符
buf:存数据的缓冲区
count:缓冲区大小 1024 4096
返回值: 0:读到文件末尾。
成功:读到的字节数。
失败:-1, 设置 errno
-1: 并且 errno = EAGIN 或 EWOULDBLOCK, 说明不是read失败,而是read在以非阻塞方式读一个设备文件(网络文件),并且文件无数据。
4.4.write
ssize_t write(int fd, const void *buf, size_t count);
参数:fd:文件描述符
buf:待写出数据的缓冲区
count:实际要写出的数据的大小
返回值:成功:写入的字节数。
失败:-1, 设置 errno
4.5perror
//输出参数:要提示的信息
//这个函数会自动与errno结合,并在提示信息后给出与errno对应的错误信息
void perror(const char *s);
//使用
perror("open error");
4.6.fcntl
#include <fcntl.h>
参数:
fd :文件标识符
op :命令 通常有 设置文件状态: F_SETFL
获取文件状态: F_GETFL
int fcntl(int fd, int op, ... /* arg */ );
/*例子*/
//把文件标识符fd对应的文件属性加入O_NONBLOCK属性
int flgs = fcntl(fd, F_GETFL); //获取文件属性
flgs |= O_NONBLOCK //添加文件属性
fcntl(fd, F_SETFL, flgs); //设置文件属性
改变一个【已经打开】的文件的 访问控制属性。
4.7.lseek
#include <unistd.h>
参数:
fd:文件描述符
offset: 偏移量
whence:起始偏移位置: SEEK_SET/SEEK_CUR/SEEK_END
返回值:
成功:较起始位置偏移量
失败:-1 errno
off_t lseek(int fd, off_t offset, int whence);
应用场景:
1. 设置文件中光标的位置,以便在读取后写入或写入后读取。
2. 使用lseek获取文件大小
3. 使用lseek拓展文件大小:要想使文件大小真正拓展,必须引起IO操作。
使用 truncate 函数,直接拓展文件。 int ret = truncate("dict.cp", 250);
五、文件系统
5.1.inode和dentry
inode
其本质为结构体,存储文件的属性信息。如:权限、类型、大小、时间、用户、盘块位置……也叫作文件属性管理结构,大多数的inode都存储在磁盘上。少量常用、近期使用的inode会被缓存到内存中。
dentry
目录项,其本质依然是结构体,重要成员变量有两个 {文件名,inode,...},而文件内容(data)保存在磁盘盘块中。
5.2.stat和lstat
都是用于获取文件属性,(从inode结构体中获取)
stat:会穿透符号连接
lstat:不会穿透符号连接
#include <sys/stat.h>
参数:
path: 文件路径
buf:(传出参数) 存放文件属性。
返回值:
成功: 0
失败: -1 errno
int stat(const char *restrict pathname,
struct stat *restrict statbuf);
struct stat
{
dev_t st_dev; /* 包含文件的设备ID */
ino_t st_ino; /* Inode号 */
mode_t st_mode; /* 文件类型和模式 */
nlink_t st_nlink; /* 硬链接数 */
uid_t st_uid; /* 所有者的用户ID */
gid_t st_gid; /* 所有者组ID */
dev_t st_rdev; /* 设备ID(如果特殊文件) */
off_t st_size; /* 总大小,以字节为单位 */
blksize_t st_blksize; /* 文件系统I/O的块大小 */
blkcnt_t st_blocks; /* 已分配的512b块数量 */
};
S_IFMT 0170000 bit mask for the file type bit field
S_IFSOCK 0140000 socket
S_IFLNK 0120000 symbolic link
S_IFREG 0100000 regular file
S_IFBLK 0060000 block device
S_IFDIR 0040000 directory
S_IFCHR 0020000 character device
S_IFIFO 0010000 FIFO
/* 要测试一个常规文件(例如),可以这样写 */
stat(pathname, &sb);
if ((sb.st_mode & S_IFMT) == S_IFREG)
{
/* Handle regular file */
}
/*由于上述形式的测试很常见,POSIX定义了额外的宏,以允许在st_mode中更简洁地编写文件类型的测试 */
S_ISREG(m)是一个常规文件吗?
S_ISDIR (m)目录吗?
S_ISCHR(m)字符设备?
S_ISBLK(m)块设备?
S_ISFIFO(m) FIFO(命名管道)?
s_islink (m)符号链接?(POSIX.1-1996中没有。)
S_ISSOCK (m)套接字?(POSIX.1-1996中没有。)
/* 因此,前面的代码片段可以重写为 */
stat(pathname, &sb);
if (S_ISREG(sb.st_mode))
{
/* Handle regular file */
}
//使用实例:查看test.c的文件类型 文件大小
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/stat.h>
int main(int argc, char *argv[])
{
struct stat sbuf;
int ret = stat(argv[1], &sbuf);
if(ret == -1)
{
perror("stat error");
exit(1);
}
//查看test.c的文件类型
if(S_ISREG(sb.st_mode))
{
printf("IT IS A REGULAR\n");
}else if (S_ISDIR(sb.st_mode))
{
printf("IT IS A DIR \n");
}else if (S_ISFIFO(sb.st_mode))
{
printf("IT IS A PIPE \n");
}else if (S_ISLNK(sb.st_mode))
{
printf("IT IS A SYM\n");
}
// 查看test.c的文件大小
// 获取文件大小: buf.st_size
// 获取文件类型: buf.st_mode
// 获取文件权限: buf.st_mode
// 符号穿透:stat会。lstat不会。
printf("file size:%ld\n",sbuf.st_size);
return 0;
}
5.3.access函数
测试指定文件是否存在/拥有某种权限。
int access(const char *pathname, int mode); 成功/具备该权限:0;失败/不具备 -1 设置errno为相应值。
参数2:R_OK、W_OK、X_OK
通常使用access函数来测试某个文件是否存在。F_OK
5.4.chmod函数
修改文件的访问权限
int chmod(const char *path, mode_t mode); 成功:0;失败:-1设置errno为相应值
int fchmod(int fd, mode_t mode);
5.5.truncate函数
截断文件长度成指定长度。常用来拓展文件大小,代替lseek。
int truncate(const char *path, off_t length); 成功:0;失败:-1设置errno为相应值
int ftruncate(int fd, off_t length);
5.6.link和unlink函数
link:相当于使用ln命令创建硬连接的部分
#include <unistd.h>
/*
创建一个硬连接,和旧路径的指向一样
oldpath:旧路径
newpath:新路径
*/
int link(const char *oldpath, const char *newpath);
unlink:删除一个文件的目录项:
#include <unistd.h>
int unlink(const char *pathname);
注意:Linux下删除文件的机制是,不断将st_nlink -1,直至减到0为止。无目录项对应的文件,将会被操作系统择机释放。(具体时间由系统内部调度算法决定)。 因此,我们删除文件,从某种意义上说,只是让文件具备了被释放的条件。
unlink函数的特征:清除文件时,如果文件的硬链接数到0了,没有dentry对应,但该文件仍不会马上被释放。要等到所有打开该文件的进程关闭该文件,系统才会挑时间将该文件释放掉。
5.7.readlink命令/函数
readlink命令:读符号链接本身,即如果创建一个软连接,用cat查看,查看的是连接到的文件,用readlink查看,查看的是软连接连接到的文件的文件路径
readlink函数:
#include <unistd.h>
/*
pathnameL:路径名
buf:缓存区
bufsiz:尺寸
*/
ssize_t readlink(const char *restrict pathname,
char *restrict buf,
size_t bufsiz);
5.8.rename函数
重命名文件
#include <stdio.h>
int rename(const char *oldpath, const char *newpath);
六、目录操作
6.1.opendir、readdir、closedir
练习示例:Linux中实现ls功能-CSDN博客
#include <sys/types.h>
#include <dirent.h>
/*打开目录 失败返回NULL*/
DIR *opendir(const char *name);
/*读目录 失败返回NULL并设置errno 读取结束时也返回NULL*/
struct dirent *readdir(DIR *dirp);
/*结构体中重要的是inode和dname*/
struct dirent
{
ino_t d_ino; /* Inode number */
off_t d_off; /* Not an offset; see below */
unsigned short d_reclen; /* Length of this record */
unsigned char d_type; /* Type of file; not supported
by all filesystem types */
char d_name[256]; /* Null-terminated filename */
};
/*关闭目录*/
int closedir(DIR *dirp);
实现ls功能:
6.2dup和dup2函数
用于重定向文件,主要用dup2。
dup2:将oldfd对应的文件复制到newfd对应的文件,简单来说就是,oldfd不变,newfd变成和oldfd一样的内容。oldfd和newfd都是文件描述符。
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
七、进程
7.1.进程简介
进程:
程序:死的。只占用磁盘空间。 ——剧本。
进程;活的。运行起来的程序。占用内存、cpu等系统资源。 ——戏。
7.2.PCB进程控制块
PCB进程控制块包含:
进程id
文件描述符表
进程状态: 初始态、就绪态、运行态、挂起态、终止态。
进程工作目录位置
*umask掩码
信号相关信息资源。
用户id和组id
7.3.环境变量
环境变量,是指在操作系统中用来指定操作系统运行环境的一些参数。通常具备以下特征: ① 字符串(本质)
② 有统一的格式:名=值[:值]
③ 值用来描述进程环境信息。
存储形式:与命令行参数类似。char *[]数组,数组名environ,内部存储字符串,NULL作为哨兵结尾。
使用形式:与命令行参数类似。
加载位置:与命令行参数类似。位于用户区,高于stack的起始位置。
引入环境变量表:须声明环境变量。extern char ** environ;
7.4.进程控制
fork函数:Linux使用fork函数创建N个进程-CSDN博客
创建一个子进程,创建成功后父进程返回子进程的ID号,子进程返回0。
#include <unistd.h>
pid_t fork(void);
getpid函数:
获取当前进程ID
getppid函数:
获取当前进程的父进程ID
#include <unistd.h>
pid_t getpid(void);
pid_t getppid(void);
7.5.进程共享
父子进程间遵循读时共享写时复制的原则。
【重点】父子进程共享:1. 文件描述符(打开文件的结构体) 2. mmap建立的映射区 (进程间通信详解)
父子进程相同:
刚fork后。 data段、text段、堆、栈、环境变量、全局变量、宿主目录位置、进程工作目录位置、信号处理方式
父子进程不同:
进程id、返回值、各自的父进程、进程创建时间、闹钟、未决信号集
父子进程共享:
读时共享、写时复制。———————— 全局变量。
1. 文件描述符 2. mmap映射区。
7.6.exec函数族
fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变。
将当前进程的.text、.data替换为所要加载的程序的.text、.data,然后让进程从新的.text第一条指令开始执行,但进程ID不变,换核不换壳。
#include <unistd.h>
int execl(const char *pathname, const char *arg, ...
/*, (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/*, (char *) NULL */);
例子:在子进程中调用 ls
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
int main(int argc, char *argv[])
{
int i;
pid_t pid = fork();
if(pid == -1){
perror("fork error");
exit(1);
}else if (pid == 0){
execlp("ls","ls","-a","-h","-l",NULL);
perror("exec error");
exit(1);
}else if (pid > 0){
sleep(1);
printf("我是父进程:%d\n", getpid());
}
return 0;
}
7.7.孤儿进程
孤儿进程: 父进程先于子进程结束,则子进程成为孤儿进程,子进程的父进程成为init进程,称为init进程领养孤儿进程。
7.8.僵尸进程
僵尸进程: 进程终止,父进程尚未回收,子进程残留资源(PCB)存放于内核中,变成僵尸(Zombie)进程。杀死僵尸进程的父进程即可清除僵尸进程。
7.9.wait函数
父进程调用wait函数可以回收子进程终止信息。该函数有三个功能:
① 阻塞等待子进程退出
② 回收子进程残留资源
③ 获取子进程结束状态(退出原因)。
#include <sys/wait.h>
pid_t wait(int *_Nullable wstatus);
例子:在父进程中用wait等待子进程运行完毕,之后回收子进程。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
pid_t pid, wpid;
int status;
pid = fork();
if(pid == 0){
printf("--我是子进程,我的进程ID是:%d, 我10秒后死亡\n",getpid());
sleep(10);
printf("--------子进程死亡-------\n");
}else if (pid > 0){
wpid = wait(&status);//如果不关心子进程为何终止,只回收子进程,则参数可以是NULL
if(wpid == -1){
perror("wait error");
exit(1);
}
printf("-------父进程等待完成:%d\n", wpid);
}else{
perror("fork");
}
return 0;
}
可使用wait函数传出参数status来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组:
1. WIFEXITED(status) 为真 → 进程正常结束
WEXITSTATUS(status) 如上宏为真,使用此宏 → 获取进程退出状态 (exit的参数)
2. WIFSIGNALED(status) 为真 → 进程异常终止
WTERMSIG(status) 如上宏为真,使用此宏 → 取得使进程终止的那个信号的编号。
kill 程序终止编号即信号:
:~$ kill -l
1) SIGHUP 2) SIGINT 3) SIGQUIT 4) SIGILL 5) SIGTRAP
6) SIGABRT 7) SIGBUS 8) SIGFPE 9) SIGKILL 10) SIGUSR1
11) SIGSEGV 12) SIGUSR2 13) SIGPIPE 14) SIGALRM 15) SIGTERM
16) SIGSTKFLT 17) SIGCHLD 18) SIGCONT 19) SIGSTOP 20) SIGTSTP
21) SIGTTIN 22) SIGTTOU 23) SIGURG 24) SIGXCPU 25) SIGXFSZ
26) SIGVTALRM 27) SIGPROF 28) SIGWINCH 29) SIGIO 30) SIGPWR
31) SIGSYS 34) SIGRTMIN 35) SIGRTMIN+1 36) SIGRTMIN+2 37) SIGRTMIN+3
38) SIGRTMIN+4 39) SIGRTMIN+5 40) SIGRTMIN+6 41) SIGRTMIN+7 42) SIGRTMIN+8
43) SIGRTMIN+9 44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9 56) SIGRTMAX-8 57) SIGRTMAX-7
58) SIGRTMAX-6 59) SIGRTMAX-5 60) SIGRTMAX-4 61) SIGRTMAX-3 62) SIGRTMAX-2
63) SIGRTMAX-1 64) SIGRTMAX
7.10.waitpid函数
作用同wait,但可指定pid进程清理,可以不阻塞。
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *_Nullable wstatus, int options);
参数:
pid:指定回收某一个子进程pid
> 0: 待回收的子进程pid
-1:任意子进程
0:同组的子进程。
status:(传出) 回收进程的状态。
options:WNOHANG 指定回收方式为非阻塞。
返回值:
> 0 : 表成功回收的子进程 pid
0 : 函数调用时, 参3 指定了WNOHANG, 并且,没有子进程结束。
-1: 失败。errno
注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。
八、进程间通信
8.1.IPC方法
Linux环境下,进程地址空间相互独立,每个进程各自有不同的用户地址空间。任何一个进程的全局变量在另一个进程中都看不到,所以进程和进程之间不能相互访问,要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进程2再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信(IPC,InterProcess Communication)。
在进程间完成数据传递需要借助操作系统提供特殊的方法,如:文件、管道、信号、共享内存、消息队列、套接字、命名管道等。随着计算机的蓬勃发展,一些方法由于自身设计缺陷被淘汰或者弃用。现今常用的进程间通信方式有:
① 管道 (使用最简单) ② 信号 (开销最小) ③ 共享映射区 (无血缘关系) ④ 本地套接字 (最稳定)
8.2.管道
实现原理: 内核借助环形队列机制,使用内核缓冲区实现。
#include <unistd.h>
/*
参数:
pipefd[0]:读端
pipefd[1]:写端
返回值:
0:创建管道成功并打开
-1:创建失败
*/
int pipe(int pipefd[2]);
特质:1. 伪文件
2. 管道中的数据只能一次读取。
3. 数据在管道中,只能单向流动。
局限性:1. 自己写,不能自己读。
2. 数据不可以反复读。
3. 半双工通信。
4. 血缘关系进程间可用。
例子:用fork()创建子进程,使用管道将数据从父进程传递至子进程。
注意:父进程调用pipe函数创建管道,得到两个文件描述符fd[0]、fd[1]指向管道的读端和写端。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int ret;
int fd[2];
pid_t pid;
char *str = "hello pipe\n";
char buf[1024]; //用于子进程读取管道内容
ret = pipe(fd);
if (ret == -1)
sys_err("pipe error");
pid = fork();
if (pid > 0) {
close(fd[0]); // 父进程关闭读端
sleep(3); // 保证父进程后结束,保证打印格式美观
write(fd[1], str, strlen(str));//父进程向子进程写入str,通过管道
close(fd[1]); //关闭写端(即关闭父进程)
} else if (pid == 0) {
close(fd[1]); // 子进程关闭写端
ret = read(fd[0], buf, sizeof(buf)); //读取管道中的内容
printf("child read ret = %d\n", ret);
write(STDOUT_FILENO, buf, ret); //向屏幕打印内容
close(fd[0]);
}
return 0;
}
读管道:
1. 管道中有数据:
(1) read返回实际读到的字节数。
2. 管道中无数据:
(1) 管道写端被全部关闭,read返回0 (好像读到文件结尾)
(2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu)
写管道:
1. 管道读端全部被关闭:
(1) 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)
2. 管道读端没有全部关闭:
(1) 管道已满,write阻塞。
(2) 管道未满,write将数据写入,并返回实际写入的字节数。
练习:使用管道实现父子进程间通信,完成:ls | wc –l。假定父进程实现ls,子进程实现wc。ls命令正常会将结果集写出到stdout,但现在会写入管道的写端;wc –l 正常应该从stdin读取数据,但此时会从管道的读端读。
解读:ls | wc -l 作用是统计文件个数
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
void sys_err(const char *str)
{
perror(str);
exit(1);
}
int main(int argc, char *argv[])
{
int ret;
int fd[2];
pid_t pid;
ret = pipe(fd); //创建管道
if(ret == -1)
{sys_err("pipe error");}
//pid>0和pid==0中执行的内容顺序将影响打印的效果
pid = fork(); //创建子进程
if(pid > 0){ //父进程
close(fd[1]); //关闭读
dup2(fd[0],STDIN_FILENO);//将标准输入重定向到fd[0]
execlp("wc","wc","-l",NULL);
}else if (pid == 0) {
close(fd[0]); //关闭写
dup2(fd[1],STDOUT_FILENO); //将标准输入重定向到fd[1]
execlp("ls","ls",NULL);
}
return 0;
}