您的位置:首页 > 运维架构 > Linux

【linux草鞋应用编程系列】_6_ 重定向和VT100编程

2013-12-17 09:45 459 查看
一、文件重定向

我们知道在linux shell 编程的时候,可以使用文件重定向功能,如下所示:

[root@localhost pipe]# echo "hello world"
hello world                    //没有进行重定向,在终端显示
[root@localhost pipe]# echo "hello world" > txt            //进行重定向,不在终端显示
[root@localhost pipe]# cat txt       //查看生成的文件 txt 的内容
hello world
[root@localhost pipe]#


在linux系统中,通过复制文件描述符实现文件的重定向。

1、复制文件描述符
linux系统中,通过函数 dup( ) 和 dup2()两个函数实现文件描述符的复制。 其原型如下:

DUP(2)                     Linux Programmer’s Manual                    DUP(2)
NAME
dup, dup2 - duplicate a file descriptor    //复制文件描述符
SYNOPSIS
#include <unistd.h>

int dup(int oldfd);   //要被替代掉的文件描述符,  旧文件描述符

int dup2(int oldfd,     //要被替代掉的文件描述符,  旧文件描述符
int newfd);  //替代的文件描述符, 新文件描述符


返回值:
成功返回新的文件描述符, 失败返回-1 。

下面我们实现一个程序,就是将标准输出设备进行重定向, 即实现 类似于 shell 中 > 的功能。
Exp: 如何将文件描述符进行定向 main.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc,char* argv[])
{
int fd;

fd=open(argv[1], O_RDWR|O_CREAT, 0666);
if(-1 == fd )
{
perror("open");
exit(1);
}

fd=dup2(fd,1);     //将 fd 指向了 stdout , 即将 stdout 重定向到了  fd 描述的文件
if(-1 == fd)
{
perror("dup2");
exit(1);
}

write(1,"abc\n",sizeof("abc\n"));

printf("this statement can not write to stdout.\n");

close(fd);
return 0;
}


程序的执行过程如下:

[root@localhost redirectory]# ll
总计 12
-rwxr-xr-x 1 root root 5436 12-12 14:18 a.out
-rw-r--r-- 1 root root  468 12-12 14:18 main.c
[root@localhost redirectory]# ./a.out txt         //可以看到 printf函数没有输出,而通过  write(1,xx,xx) 的信息也没有输出到stdout
[root@localhost redirectory]# cat txt   //数据写入到了txt, 查看txt 的内容
abc
[root@localhost redirectory]#


上面的执行结果,说明我们将标准输出重定向到了 fd 描述的文件 txt. 同时也说明了在重定向 stdout 后,printf
函数也不会将数据输出标准输出,或者重定向后的文件。

为了对比我们进行一下改变, 修改后的 main.c :

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc,char* argv[])
{
int fd;

fd=open(argv[1], O_RDWR|O_CREAT, 0666);
if(-1 == fd )
{
perror("open");
exit(1);
}
#if 0
fd=dup2(fd,1);
if(-1 == fd)
{
perror("dup2");
exit(1);
}
#endif
write(1,"abcdefg\n",sizeof("abcdefg\n"));

printf("this statement can  write to stdout.\n");

close(fd);
return 0;
}


执行结果如下:

[root@localhost redirectory]# vim main.c
[root@localhost redirectory]# gcc main.c
[root@localhost redirectory]# cat txt
abc
[root@localhost redirectory]# ./a.out txt
abcdefg              //write(1,xx,xx) 正常从标准输出输出
this statement can  write to stdout.   //printf 也正常输出到标准输出
[root@localhost redirectory]# cat txt
abc
[root@localhost redirectory]#


dup函数不能实现重定向,只是实现分配一个新的文件描述符。
Exp:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>

int main(int argc,char* argv[])
{
int fd;

fd=dup(1);

printf("the new fd is: %d\n",fd);
write(fd,"abcdefg\n",sizeof("abcdefg\n"));

close(fd);
return 0;
}


程序的执行过程如下:

[root@localhost redirectory]# vim dup.c
[root@localhost redirectory]# gcc dup.c
[root@localhost redirectory]# ./a.out
the new fd is: 3      //默认dup()成功返回最小的未分配的文件描述符
abcdefg      //通过fd, 实现了对 stdout = 1 标准输出的写操作
[root@localhost redirectory]#


二、VT 100 编程
在linux中可以通过 VT100 码对终端进行设置,要设置终端,让 printf 输出VT100 指令码就可以实现。VT100控制
指令码英语的原文为: ESC sequences 。 翻译成指令码、控制码都好像不能完整的表述其意义;我们这里用指令码对
来指代上面的: ESC sequences 。
VT100 终端控制机制的指令码都以 ESC 的ASCII开始(ESC 字符的 ASCII 码值 = '\033' ) , 通过在C程序中利用 printf
来输出VT100的指令码改变终端的样式。

说了这么多,好像不得要领,因为VT100是一个非常复杂的机制,英文的介绍文档长达100多页,我们直接进行一次
体验,就能有一个感性的认识了。
Exp: 1 在整个终端界面输出 大写的 E 字符, 指令码为 ESC #8

#include <stdio.h>

int main(void)
{
printf("\033#8");
return 0;
}


程序的执行效果如下所示:

EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
[root@localhost vt100]# EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE


这样整个屏幕就输被大写的字符 E 给铺满了。

这时候,如果在终端输入 reset 命令,就可以恢复到正常状态。或者一直按住 Enter 键直到出现正常的界面,
或者用快捷键 Ctrl + l (小写字母 l ,不是数字1 )也可以恢复正常界面。

要实现上面的效果,还可以利用 echo 命令实现:

[root@localhost vt100]# echo -ne  "\033#8"


上面的命令同样可以实现 printf("\033#8"), 的效果。

Exp: 将光标一直回退到终端的第一行、第一列。

#include <stdio.h>

int main(void)
{
for(;;)
{
printf("\033[H");
printf("0123456789abcdefghijklmnopqrstuvwxyz");
}
return 0;
}


执行的时候,在终端窗口的最上面的第一列一直输出。 具体效果可以自己测试一下。
上面的效果还可以用 printf("\033[1;1H"); 实现; 指令码 "\033[x;yH" 表示将光标位置设置到 ( x,y )处, x,y
从1开始计数。

还可以改变输出的颜色: 前景色(即字符显示颜色)和背景色。 前景、背景均支持8种颜色,设置前景色用指令码:"\033[30m" 到 "\033[37m" ; 设置背景色用指令 "\033[40m" 到 "\033[47m" .
前景色:
"\033[30m" 黑色
"\033[31m" 红色
"\033[32m" 绿色
"\033[33m" 黄色
"\033[34m" 蓝色
"\033[35m" 紫色
"\033[36m" 深绿
"\033[37m" 白色

背景色:
"\033[40m" 黑色
"\033[41m" 深红
"\033[42m" 绿色
"\033[43m" 黄色
"\033[44m" 蓝色
"\033[45m" 紫色
"\033[46m" 深绿
"\033[47m" 白色

Exp: 改变中断的输出颜色。 main.c

#include <stdio.h>

int main(void)
{
int i;
for(i=0;i<3;i++)
{
printf("\033[37m\033[40m");
printf("0123456789abcdefghijklmnopqrstuvwxyz\n");
printf("\033[30m\033[47m");
printf("0123456789abcdefghijklmnopqrstuvwxyz\n");
}
return 0;
}


执行效果具体可以测试一下,为灰色和黑色交替的颜色。



三、系统时钟
在linux中可以通过系统提供的函数,获取系统的时间信息; 通过time 函数族可以获取系统的时间。time函数族
的原型如下分述如下:
time函数:

TIME(2)                    Linux Programmer’s Manual                   TIME(2)
NAME
time - get time in seconds    //获取从1970年1月1日0时0分0秒 到 当前时刻的 总秒数
SYNOPSIS
#include <time.h>

time_t time(time_t *t);  //输出参数,存储获取的秒


返回值:
成功返回获取的秒, 失败返回 ((time_t)-1) .

Exp: 打印从1970年1月1日0时0分0秒到现在经过了多少 秒 。

#include <stdio.h>
#include <time.h>

int main(void)
{
time_t t;

printf("From 1970 01-01 0:00:00 have past secnods:%ld\n", time(&t));

return 0;
}


执行结果如下所示 :

[root@localhost time]# ./a.out
From 1970 01-01 0:00:00 have past secnods:1386835671


通过time函数得到的系统时钟 可读性很差,linux提供了一套转换和格式的函数来使这个时间更易读。下面一些函数
实现转换和格式的功能:

CTIME(3)                   Linux Programmer’s Manual                  CTIME(3)
NAME
asctime,  ctime,  gmtime,  localtime, mktime, asctime_r, ctime_r, gmtime_r, local-
time_r - transform date and time to broken-down time or ASCII
SYNOPSIS
#include <time.h>

char *asctime(const struct tm *tm);
char *asctime_r(const struct tm *tm, char *buf);

char *ctime(const time_t *timep);
char *ctime_r(const time_t *timep, char *buf);

struct tm *gmtime(const time_t *timep);
struct tm *gmtime_r(const time_t *timep, struct tm *result);

struct tm *localtime(const time_t *timep);
struct tm *localtime_r(const time_t *timep, struct tm *result);

time_t mktime(struct tm *tm);


这里需要用到一个结构体类型:struct tm ,
其定义如下:

struct tm {
int tm_sec;         /* seconds */   //秒
int tm_min;         /* minutes */   //分
int tm_hour;        /* hours */       //时
int tm_mday;        /* day of the month */    //一个月的第几天, 即 日
int tm_mon;         /* month */    //月
int tm_year;        /* year */        //年, 从1900年到现在的年数
int tm_wday;        /* day of the week  */   //星期几
int tm_yday;        /* day in the year */       //一年中的第几天
int tm_isdst;       /* daylight saving time */
};


Exp: 输出年月日 星期 时分秒

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main(void)
{
time_t t;
struct tm tm;
void* p_tmp;
char  buf[32];

time(&t);
p_tmp=localtime_r(&t,&tm);
if(NULL == p_tmp)
{
perror("get local time");
exit(1);
}

sprintf(buf,"%d-%d-%d %d %d-%d-%d", 1900 + tm.tm_year, 1+ tm.tm_mon,tm.tm_mday,
tm.tm_wday,
tm.tm_hour,tm.tm_min,tm.tm_sec);

printf("%s\n",buf);

return 0;
}


输出结果如下:

[root@localhost time]# ./a.out
2013-12-12 4 16-30-4         //2013年11月12日 星期4  16:30:04


需要注意的是:

tm结构体中的年是从 1900 年到现在经过的年数, 而月的计数是从0 开始的,因此要得到年需要在 tm.tm_year
的基础上加上1900 , 月份则需要在 tm.tm_mon 的基础上加上 1 。

事实上系统还提供了更加方便的格式化函数: strftime, 其原型如下:

STRFTIME(3)                Linux Programmer’s Manual               STRFTIME(3)
NAME
strftime - format date and time

SYNOPSIS
#include <time.h>

size_t strftime(char *s,   //存储格式化后的字符串
size_t max,  //字符串的最大长度
const char *format,  //格式化字符串
const struct tm *tm);  //struct time 的结构体指针


返回值:

返回写入到 char* s 的字符数, 不包括s 后的 '\0' 字符。

Exp: 改进后的显示 年月日 星期 时分秒 的代码

#include <stdio.h>
#include <time.h>
#include <stdlib.h>

int main(void)
{
time_t t;
struct tm tm;
void* p_tmp;
char  buf[64];

//clear screen
printf("\033[2J");
for(;;)
{
time(&t);
p_tmp=localtime_r(&t,&tm);
if(NULL == p_tmp)
{
perror("get local time");
exit(1);
}

strftime(buf,64,"%Y-%m-%d %A %T %z",&tm);

printf("\033[H\033[34m");
printf("%s\n",buf);
sleep(1);
}
return 0;
}


  执行结果可自己测试一下。

另类小技巧:
我们知道,在linux 下,输入子系统的设备文件在 /dev/input/ 目录下。我的系统如下所示:

[root@localhost input]# ls
event0  event1  event2  event3  event4  js0  mice  mouse0  mouse1  mouse2
[root@localhost input]#


event0-event4 表示键盘设备, 键盘具体具体挂载在那一个设备节点文件上可以通过下面的测试判断出来,
通常第一个键盘挂载在 /dev/input/event0 上。
mice: 表示鼠标设备节点文件,
mouse0-mouse2 : 表示鼠标设备。
js0 : 表示的游戏手柄的设备节点文件。

可以测试一下: cat /dev/input/event0 然后敲下键盘输入字符,Ctrl + c,如果出现乱码不能恢复,那
么在终端输入 reset 命令即可恢复, 输入reset 时看到的还是乱
码, 或者输入: echo -ne "\033["

cat /dev/input/mice 然后移动鼠标,会看到一些出现乱码
如果想结束测试: 按下 Ctrl + c

  【Linux草鞋应用编程系列】_6_重定向和VT100编程
   欢迎批评指正,也欢迎拍砖。
   本系列文章,未完待续......
  【linux草鞋应用编程系列】_5_ Linux网络编程
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: