tee这个程序读完,主要学习到了文件输入输出,读写的结果系统调用,更重要的是程序的简洁和为标准化做的努力。
/* tee - read from standard input and write to standard output and files.
从标准输入中读,写到标准输出和文件中去
Copyright (C) 85,1990-2006 Free Software Foundation, Inc.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2, or (at your option)
any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software Foundation,
Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
/* Mike Parker, Richard M. Stallman, and David MacKenzie */
#include
#include
#include
#include
#include "system.h"
#include "error.h"
#include "stdio--.h"//这个名字起得。。。
/* The official name of this program (e.g., no `g' prefix). */
#define PROGRAM_NAME "tee"//定义宏
#define AUTHORS "Mike Parker", "Richard M. Stallman", "David MacKenzie"//定义宏
//函数原型声明,此函数接受两个形参,一个是整型,一个是指向常量字符的指针的指针,函数返回的类型是布尔类型,作用域仅限于本文件
static bool tee_files (int nfiles, const char **files);
/* If true, append to output files rather than truncating them.这句话的意思是,如果append这个布尔类型为真的话,向输出文件中追加,而不是截断他们 */
static bool append;//声明全局布尔变量
/* If true, ignore interrupts.如果这个布尔变量是真的话,忽略中断,我测试了一下,按^C的确不管用了,^D还是可以的 */
static bool ignore_interrupts;//声明全局布尔变量,在soureinsight的左边缩略图里,也显示出了这些全局变量了
/* The name that this program was run with. */
char *program_name;//声明全局字符指针变量
static struct option const long_options[] =
{
{"append", no_argument, NULL, 'a'},
{"ignore-interrupts", no_argument, NULL, 'i'},
{GETOPT_HELP_OPTION_DECL},
{GETOPT_VERSION_OPTION_DECL},
{NULL, 0, NULL, 0}
};//初始化一个option类型的结构体数组
void
usage (int status)//帮助函数
{
if (status != EXIT_SUCCESS)
fprintf (stderr, _("Try `%s --help' for more information.\n"),
program_name);
else
{
printf (_("Usage: %s [OPTION]... [FILE]...\n"), program_name);
fputs (_("\
Copy standard input to each FILE, and also to standard output.\n\
\n\
-a, --append append to the given FILEs, do not overwrite\n\
-i, --ignore-interrupts ignore interrupt signals\n\
"), stdout);
fputs (HELP_OPTION_DESCRIPTION, stdout);
fputs (VERSION_OPTION_DESCRIPTION, stdout);
fputs (_("\
\n\
If a FILE is -, copy again to standard output.\n\
"), stdout);
printf (_("\nReport bugs to <%s>.\n"), PACKAGE_BUGREPORT);
}
exit (status);
}
int
main (int argc, char **argv)//main函数不算长,一屏而已
{
bool ok;//声明布尔类型变量
int optc;//声明整型变量
initialize_main (&argc, &argv);
program_name = argv[0];
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
atexit (close_stdout);//登记出口函数
append = false;//初始化布尔变量append的值为假
ignore_interrupts = false;//同上
while ((optc = getopt_long (argc, argv, "ai", long_options, NULL)) != -1)//while循环里只有一个case判断,循环条件是调用库函数getopt_long,对命令行选项进行解析,全部解析一遍
{
switch (optc)//optc是在循环初始,通过getopt_long得到的命令行选项
{
case 'a'://如果命令行里有-a,则将布尔变量append的值设置为真
append = true;
break;
case 'i':
ignore_interrupts = true;
break;
case_GETOPT_HELP_CHAR;
case_GETOPT_VERSION_CHAR (PROGRAM_NAME, AUTHORS);
default:
usage (EXIT_FAILURE);//默认,以上条件都不满足的话,调用帮助函数,并退出程序
}
}//while循环结束,循环的作用是解析命令行选项,并视情况看是否设置两个全局布尔变量
if (ignore_interrupts)//这里比较有意思啊,如果ignore_interrupts的值是真的话,就调用signal函数,signal函数做的事情就是对中断进行了忽略,apue上有详细说明
signal (SIGINT, SIG_IGN);
/* Do *not* warn if tee is given no file arguments.
POSIX requires that it work when given no arguments.
正好看到apue的标准化,看来下面的这段代码考虑到了POSIX标准 */
ok = tee_files (argc - optind, (const char **) &argv[optind]);//这里调用下面的tee_files函数,并将返回值放在ok这个变量里,这个变量在最后程序退出的时候用来决定返回值,是成功还是失败
if (close (STDIN_FILENO) != 0)//关闭标准输入
error (EXIT_FAILURE, errno, _("standard input"));
exit (ok ? EXIT_SUCCESS : EXIT_FAILURE);//退出程序
}
/* Copy the standard input into each of the NFILES files in FILES
and into the standard output.
Return true if successful. */
static bool
tee_files (int nfiles, const char **files)//tee_files的具体实现,tee的意思是高尔夫球的发球基座
{
FILE **descriptors;//声明指向文件的指针的指针
char buffer[BUFSIZ];//声明一个字符数组,在文件操作中经常使用了
ssize_t bytes_read; //ssize_t类型的变量,查了一下,ssize_t 就是 signed int /long,32位系统上是有符号整型,64位系统上是有符号长整型
int i;//声明整型变量
bool ok = true;//初始化布尔变量ok的值为真,ok用来保存最后函数返回的值
char const *mode_string =
(O_BINARY
? (append ? "ab" : "wb")
: (append ? "a" : "w"));//这个条件语句学习了,节省了很多if判断,也没有造成多少的可读性降低,实在是受教,希望自己将来也能写出这样简洁的代码来^_^
descriptors = xnmalloc (nfiles + 1, sizeof *descriptors);//为descriptors分配存储空间
/* Move all the names `up' one in the argv array to make room for
the entry for standard output. This writes into argv[argc]. */
for (i = nfiles; i >= 1; i--)
files[i] = files[i - 1];//这里不太理解,数组的元素都后移一个
if (O_BINARY && ! isatty (STDIN_FILENO))
freopen (NULL, "rb", stdin);//打开标准输入
if (O_BINARY && ! isatty (STDOUT_FILENO))
freopen (NULL, "wb", stdout);//打开标准输出
/* In the array of NFILES + 1 descriptors, make
the first one correspond to standard output. */
descriptors[0] = stdout;//原来是这样,将第一个输出文件设置为标准输出
files[0] = _("standard output");
setvbuf (stdout, NULL, _IONBF, 0);//调用setvbuf库函数,这里的作用是不缓冲
for (i = 1; i <= nfiles; i++)
{
//这个for循环的作用是打开需要打开的文件,并设置好不缓冲标记,想想我写的程序,直接就fopen了,其他啥都没有管,这就是符合标准程序的样子
descriptors[i] = (STREQ (files[i], "-")
? stdout
: fopen (files[i], mode_string));
if (descriptors[i] == NULL)
{
error (0, errno, "%s", files[i]);
ok = false;
}
else
setvbuf (descriptors[i], NULL, _IONBF, 0);
}
while (1)
{
bytes_read = read (0, buffer, sizeof buffer);//调用read系统调用进行读操作,失败则跳出while循环
#ifdef EINTR
if (bytes_read < 0 && errno == EINTR)
continue;
#endif
if (bytes_read <= 0)
break;
/* Write to all NFILES + 1 descriptors.
Standard output is the first one.
可以看到,这里具体实现了向目标文件中写的操作,是调用的库函数fwrite进行的,所有前面设置好的文件都写了
*/
for (i = 0; i <= nfiles; i++)
if (descriptors[i]
&& fwrite (buffer, 1, bytes_read, descriptors[i]) != bytes_read)
{
error (0, errno, "%s", files[i]);
descriptors[i] = NULL;
ok = false;
}
}
if (bytes_read == -1)//最后,如果bytes_read等于-1,说明read系统调用失败了
{
error (0, errno, _("read error"));
ok = false;
}
/* Close the files, but not standard output.
调用fclose关闭文件描述符,i从1开始,不关闭标准输出,是因为程序退出的时候会统一关闭,还记得那个标准的,登记出口函数
*/
for (i = 1; i <= nfiles; i++)
if (!STREQ (files[i], "-")
&& descriptors[i] && fclose (descriptors[i]) != 0)//其值这个if的条件语句写的也是很简洁啦
{
error (0, errno, "%s", files[i]);
ok = false;
}
free (descriptors);//释放当初申请的内存空间
return ok;//函数返回
}
ok,来strace一把了:
[root@test t]# strace tee -a file1 file2
execve("/usr/bin/tee", ["tee", "-a", "file1", "file2"], [/* 21 vars */]) = 0
uname({sys="Linux", node="test.16.com", ...}) = 0
brk(0) = 0x81ca000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=120505, ...}) = 0
old_mmap(NULL, 120505, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7fe2000
close(3) = 0
open("/lib/tls/libc.so.6", O_RDONLY) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0\20_m\000"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1454462, ...}) = 0
old_mmap(0x6c1000, 1219772, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x6c1000
old_mmap(0x7e5000, 16384, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x124000) = 0x7e5000
old_mmap(0x7e9000, 7356, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7e9000
close(3) = 0
old_mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7fe1000
mprotect(0x7e5000, 4096, PROT_READ) = 0
mprotect(0x6b9000, 4096, PROT_READ) = 0
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7fe1aa0, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
munmap(0xb7fe2000, 120505) = 0
brk(0) = 0x81ca000
brk(0x81eb000) = 0x81eb000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_LARGEFILE) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=48517104, ...}) = 0
mmap2(NULL, 2097152, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb7de1000
close(3) = 0
rt_sigaction(SIGPIPE, {SIG_IGN}, NULL, 8) = 0
open("/usr/share/locale/locale.alias", O_RDONLY) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=2528, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7de0000
read(3, "# Locale name alias data base.\n#"..., 4096) = 2528
read(3, "", 4096) = 0
close(3) = 0
munmap(0xb7de0000, 4096) = 0
open("/usr/share/locale/en_US.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en_US.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en_US/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en.UTF-8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en.utf8/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en/LC_MESSAGES/coreutils.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("file1", O_WRONLY|O_APPEND|O_CREAT|O_LARGEFILE, 0666) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7de0000
fstat64(3, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
_llseek(3, 0, [0], SEEK_SET) = 0
munmap(0xb7de0000, 4096) = 0
open("file2", O_WRONLY|O_APPEND|O_CREAT|O_LARGEFILE, 0666) = 4
fstat64(4, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7de0000
fstat64(4, {st_mode=S_IFREG|0644, st_size=0, ...}) = 0
_llseek(4, 0, [0], SEEK_SET) = 0
munmap(0xb7de0000, 4096) = 0
read(0, hello(我输入的字符串)
"hello\n", 8192) = 6
write(1, "hello\n", 6hello(向标准输出进行输出)
) = 6
write(3, "hello\n", 6) = 6(向文件描述符3进行输出)
write(4, "hello\n", 6) = 6(向文件描述符4进行输出)
read(0, seconds(我输入的字符串)
"seconds\n", 8192) = 8
write(1, "seconds\n", 8seconds
) = 8
write(3, "seconds\n", 8) = 8
write(4, "seconds\n", 8) = 8
read(0,
[root@test t]# ll
total 8
-rw-r--r-- 1 root root 14 Dec 13 12:13 file1
-rw-r--r-- 1 root root 14 Dec 13 12:13 file2
[root@test t]# sdiff file1 file2
hello hello
seconds seconds
可以看到,shell默认地将标准输出的文件描述符设置为1了,以上跟踪中主要关注的是红色标记出来的行
阅读(1206) | 评论(0) | 转发(0) |