c语言15行实现简易cat命令
刚刚和舍友打赌。舍友说PY20行能做xlsx文件分析整理,C20行屁都干不了。我说简单的cat还是能做的嘛。他说不信。我说不处理非文件的参数的话10行能做啊。
下面直接贴代码吧:
#include <stdio.h>
#include <stdlib.h>
#define assert(x) ((void)((x)||(perror(argv[0]), exit(-1), 0))) //<update171106
int main(int argc, char *argv[]) {
int ai = 1;
do {
if (argc > 1) assert(freopen(argv[ai], "rb", stdin)); //<debug
for (int c; (c = getchar()) != EOF; putchar(c)) //<update180305
;
assert(!ferror(stdin));
} while (++ai < argc); //<debug
}
Feature
- 允许多个文件合并
- 允许从标准输入输入
- 有错误反馈
运行截图:
debug
mdzz 之前些地方写错了:
13行 } while (ai++ < argc);
应该是++ai。这里参数argc
指的是argv
数组成员个数(标准要求argc >= 0 && argv[argc][0] == '\0'
)
9行 if (argc != 1) assert(freopen(argv[ai], "rb", stdin));
应该是argc > 1
。
这两个错误会导致:
- 如果目标环境不提供命令行参数,即
argc == 0
的时候,可能会导致freopen的第一个参数解析时越界,出现不可预知的状况。 - 如果调用时带参数,会导致最后一次循环
ai == argc
,间接导致freopen的第一个参数为空串。
2运行结果错误的演示:
这里最后一次freopen接收到空串,这个freopen的表现和传递NULL一样(不合理,我看POSIX标准没找到这点,反而找到了ENOENT: pathname is an empty string <- 感觉原文有二义性)
为什么我记得空串代表原本的stdin呢?
update171106:
今天有瞄了下代码,感觉那个宏不太对劲,好像加上个抛弃返回值的强制转换比较合适。
否则可能会被用来做奇怪的事情(因为返回 ((_bool)1)
):
- 后跟数组下标
assert(true)["string233"]; => "string233"[1] => 't' - 前跟函数指针(函数名)
pFun assert(true); => pFun(1) - 前跟 if while
if/while assert(true) => if/while (1)
加上 (void)
就能防止被上面那么说的乱用啦 233,然而没什么意义 hh
assert 函数宏
解释一下这个函数宏吧:
#define assert(x) ((void)((x)||(perror(argv[0]), exit(-1), 0)))
首先这个宏没有副作用(因为所有参数有且只有出现一次)。
其次这个东西还是用了不少 C 技巧的:
||
短路特性,只有参数(_bool)x == false
才执行后面的逗号运行符;见 std-keyword: sequence point & short-circuit evaluation
,
逗号运算符顺序执行的特性,所以先执行perror
再执行exit
。因为 || 运算符要求运算符两边都是标量,所以在最后让逗号表达式返回一个 int。perror(argv[0])
argv[0] 是程序的调用名字,这个输出是很标准的 linux 程序做法。
update180305:
原本那个 getchar 的循环原本是这样写的:for (char c; (c = getchar()) != EOF; putchar(c))
,这段代码有明显的错误。
错在哪?当然是char这里了:如果用char的话,在“实际应用”中会导致0xFF
和EOF
的重合。
实际上很多 C 环境的 char
都是 int8_t
,而 (int8_t)0xff
很明显是 -1
,不相信?那就试试下面的 C11 代码能不能通过编译。
// test.c
// clang -Wall -Wextra test.c -c -o cat
#include <stdint.h>
_Static_assert((int8_t)0xff == -1 && sizeof(char) == sizeof(int8_t), "it's impossible meet error!");
_Static_assert((char)0xff != -1, "in this env., char is signed!");
结果如下:
我们来分析一下
编译不通过 ->
静态断言失败 ->
(char)0xff == -1 ->
(char)0xff 算数提升到 int,且值等于负一 ->
(char)0xff 符号位扩展了 ->
(char)0xff 等价于 int8_t
所以如果一个二进制文件内有 0xff
这个字节的话,原本代码会导致误判,以为到了 EOF
而提前结束。
改成 int
就能在实际应用中避免这个问题,这也是标准函数原型这样设计的本意。
注意:二进制文件才会有这样的问题, ascii
编码文本文件没有 0xff
。
然而这里还是有一个问题,标准只要求了 sizeof(char) <= sizeof(int),会不会有一些很 gay 的环境,他们两者相等,那就无法预留一个 EOF 的值出来了,所以这个标准的原型严格的说还是存在问题的。
不过如果真的有这种环境,那在这之上很难实现通用的文件就是了。
欢迎转载©️tjua link: http://www.cnblogs.com/tjua/p/7758047.html (请保留本行)
next target: 基于 make 的简易自动构建系统
最近看到 gitlab-runner 项目后想到的脑洞。估计也是几行的事。
c语言15行实现简易cat命令的更多相关文章
- go语言之行--文件操作、命令行参数、序列化与反序列化详解
一.简介 文件操作对于我们来说也是非常常用的,在python中使用open函数来对文件进行操作,而在go语言中我们使用os.File对文件进行操作. 二.终端读写 操作终端句柄常量 os.Stdin: ...
- C+命令行+方向键=简易版扫雷
前言: 想起来做这个是因为那时候某天知道了原来黑框框里面的光标是可以控制的,而且又经常听人说起这个,就锻炼一下好了. 之前就完成了那1.0的版本,现在想放上来分享却发现有蛮多问题的,而且最重要的是没什 ...
- 【Linux】- cat命令的源码历史
转自:Cat 命令的源码历史 以前我和我的一些亲戚争论过计算机科学的学位值不值得读.当时我正在上大学,并要决定是不是该主修计算机.我姨和我表姐觉得我不应该主修计算机.她们承认知道如何编程肯定是很有用且 ...
- linux命令详解:cat命令
转:http://www.cnblogs.com/lwgdream/archive/2013/11/06/3409802.html 前言 cat命令用于读取文本文件,并且能够显示行号.特殊字符等. 使 ...
- Linux cat命令
200 ? "200px" : this.width)!important;} --> 介绍 cat命令经常会用来查看一个文件的内容,并且结合它本身的一些参数经常可以用来做一 ...
- 每天一个linux命令(10):cat 命令
cat命令的用途是连接文件或标准输入并打印.这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用. 1.命令格式: cat [选项] [文件] ...
- Linux cat命令的使用
cat命令主要用来查看文件内容,创建文件,文件合并,追加文件内容等功能. A:查看文件内容主要用法: 1.cat f1.txt,查看f1.txt文件的内容. 2.cat -n f1.txt,查看f ...
- Linux命令详解之—cat命令
cat命令的功能是连接文件或标准输入并打印,今天就为大家介绍下Linux中的cat命令. 更多Linux命令详情请看:Linux命令速查手册 Linux 的cat命令通常用来显示文件内容,也可以用来将 ...
- 每天一个linux命令(8):cat 命令
cat命令的用途是连接文件或标准输入并打印.这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用. 1.命令格式: cat [选项] [文件] ...
随机推荐
- 利用ADO让普通人用excel读取oracle数据库表的通用办法
Ref:http://blog.csdn.net/iamlaosong/article/details/8465177 Excel通过ADO方式连接到Oracle并操作Oracle给那些编程能力不强的 ...
- JavaScript自动化构建工具入门----grunt、gulp、webpack
蛮荒时代的程序员: 做项目的时候,会有大量的js 大量的css 需要合并压缩,大量时间需要用到合并压缩 在前端开发中会出现很多重复性无意义的劳动 自动化时代的程序员: 希望一切都可以自动完成 ...
- 枚举类TimeUnit
枚举类TimeUnit 全路径为 java.util.concurrent.TimeUnit TimeUnit 主要用于通知基于时间的方法如何解释给定的计时参数 举例如下 如果 lock 不可用,则以 ...
- 推荐一个比FiddlerCore好用的HTTP(S)代理服务器
为什么不用FiddlerCore? 说到FiddlerCore大家可能会比较陌生,那么它哥Fiddler就比较熟悉了:抓包.模拟低带宽.修改请求我平时比较常用.Fiddler的本质就是一个HTTP代理 ...
- [Troubleshooting] Inter VT 主板已开启,但测试工具显示未开启.
一周前给神船Z7SL3重装了Win10的系统,但安装VMware时遇到了比较诡异的事 主板确定开启了VT 但是VMware显示不支持64位 用工具Securable和Intel(R) Process ...
- jdk版本切换
安装1.6/1.7/1.8版本的jdk 保存jdk的安装目录下的文件 卸载所有jdk 将jdk各个版本拷贝到一个文件夹下 配置环境变量 因为安装之后系统会有注册表之类的文件,单纯的修改环境是不会修改成 ...
- angular学习笔记03 理论加实践
novalidate 属性是在 HTML5 中新增的.禁用了使用浏览器的默认验证. //augular.js自定义指令 .directive('runoobDirective',function(){ ...
- ioc(Inversion of Control)控制反转和DI
ioc意味着将你设计好的交给容器控制,而不是传统在你的对象中直接控制 谁控制了谁:传统的javaSE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象:而ioc是有专门一个容 ...
- TTabControl
1.TTabControl 组件的典型用法TTabControl 组件使用起来,根本不会使程序简单化,所以不提倡使用此组件,可以用TPageControl组件代替.与多页组件不同的是,虽然Tab 组件 ...
- leetCode没那么难啦 in Java (二)
介绍 本篇介绍的是标记元素的使用,很多需要找到正确元素都可以将正确元素应该插入的位置单独放置一个标记来记录,这样可以达到原地排序的效果. Start 27.RemoveElement 删除指定元 ...