刚刚和舍友打赌。舍友说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

这两个错误会导致:

  1. 如果目标环境不提供命令行参数,即argc == 0的时候,可能会导致freopen的第一个参数解析时越界,出现不可预知的状况。
  2. 如果调用时带参数,会导致最后一次循环ai == argc,间接导致freopen的第一个参数为空串。

2运行结果错误的演示:



这里最后一次freopen接收到空串,这个freopen的表现和传递NULL一样(不合理,我看POSIX标准没找到这点,反而找到了ENOENT: pathname is an empty string <- 感觉原文有二义性)

为什么我记得空串代表原本的stdin呢?

update171106:

今天有瞄了下代码,感觉那个宏不太对劲,好像加上个抛弃返回值的强制转换比较合适。

否则可能会被用来做奇怪的事情(因为返回 ((_bool)1)):

  1. 后跟数组下标

    assert(true)["string233"]; => "string233"[1] => 't'
  2. 前跟函数指针(函数名)

    pFun assert(true); => pFun(1)
  3. 前跟 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 技巧的:

  1. || 短路特性,只有参数 (_bool)x == false 才执行后面的逗号运行符; 见 std-keyword: sequence point & short-circuit evaluation
  2. , 逗号运算符顺序执行的特性,所以先执行 perror 再执行 exit。因为 || 运算符要求运算符两边都是标量,所以在最后让逗号表达式返回一个 int。
  3. perror(argv[0]) argv[0] 是程序的调用名字,这个输出是很标准的 linux 程序做法。

update180305:

原本那个 getchar 的循环原本是这样写的:for (char c; (c = getchar()) != EOF; putchar(c)),这段代码有明显的错误。

错在哪?当然是char这里了:如果用char的话,在“实际应用”中会导致0xFFEOF的重合。

实际上很多 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命令的更多相关文章

  1. go语言之行--文件操作、命令行参数、序列化与反序列化详解

    一.简介 文件操作对于我们来说也是非常常用的,在python中使用open函数来对文件进行操作,而在go语言中我们使用os.File对文件进行操作. 二.终端读写 操作终端句柄常量 os.Stdin: ...

  2. C+命令行+方向键=简易版扫雷

    前言: 想起来做这个是因为那时候某天知道了原来黑框框里面的光标是可以控制的,而且又经常听人说起这个,就锻炼一下好了. 之前就完成了那1.0的版本,现在想放上来分享却发现有蛮多问题的,而且最重要的是没什 ...

  3. 【Linux】- cat命令的源码历史

    转自:Cat 命令的源码历史 以前我和我的一些亲戚争论过计算机科学的学位值不值得读.当时我正在上大学,并要决定是不是该主修计算机.我姨和我表姐觉得我不应该主修计算机.她们承认知道如何编程肯定是很有用且 ...

  4. linux命令详解:cat命令

    转:http://www.cnblogs.com/lwgdream/archive/2013/11/06/3409802.html 前言 cat命令用于读取文本文件,并且能够显示行号.特殊字符等. 使 ...

  5. Linux cat命令

    200 ? "200px" : this.width)!important;} --> 介绍 cat命令经常会用来查看一个文件的内容,并且结合它本身的一些参数经常可以用来做一 ...

  6. 每天一个linux命令(10):cat 命令

    cat命令的用途是连接文件或标准输入并打印.这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用. 1.命令格式: cat [选项] [文件] ...

  7. Linux cat命令的使用

    cat命令主要用来查看文件内容,创建文件,文件合并,追加文件内容等功能.   A:查看文件内容主要用法: 1.cat f1.txt,查看f1.txt文件的内容. 2.cat -n f1.txt,查看f ...

  8. Linux命令详解之—cat命令

    cat命令的功能是连接文件或标准输入并打印,今天就为大家介绍下Linux中的cat命令. 更多Linux命令详情请看:Linux命令速查手册 Linux 的cat命令通常用来显示文件内容,也可以用来将 ...

  9. 每天一个linux命令(8):cat 命令

    cat命令的用途是连接文件或标准输入并打印.这个命令常用来显示文件内容,或者将几个文件连接起来显示,或者从标准输入读取内容并显示,它常与重定向符号配合使用. 1.命令格式: cat [选项] [文件] ...

随机推荐

  1. 二叉树终极教程--BinarySearchTree

    BinarySearchTreeMap 的 实现 public interface Map<K extends Comparable<K>, V> { void put(K k ...

  2. 爬虫实战:爬虫之 web 自动化终极杀手 ( 上)

    欢迎大家前往腾讯云技术社区,获取更多腾讯海量技术实践干货哦~ 作者:陈象 导语: 最近写了好几个简单的爬虫,踩了好几个深坑,在这里总结一下,给大家在编写爬虫时候能给点思路.本次爬虫内容有:静态页面的爬 ...

  3. 翻译连载 | 第 9 章:递归(上)-《JavaScript轻量级函数式编程》 |《你不知道的JS》姊妹篇

    原文地址:Functional-Light-JS 原文作者:Kyle Simpson-<You-Dont-Know-JS>作者 关于译者:这是一个流淌着沪江血液的纯粹工程:认真,是 HTM ...

  4. IOS SDWebImage实现基本原理详解(转载)

    1)当我门需要获取网络图片的时候,我们首先需要的便是URl没有URl什么都没有,获得URL后我们SDWebImage实现的并不是直接去请求网路,而是检查图片缓存中有没有和URl相关的图片,如果有则直接 ...

  5. java集合系列——List集合之LinkedList介绍(三)

    1. LinkedList的简介 JDK 1.7 LinkedList是基于链表实现的,从源码可以看出是一个双向链表.除了当做链表使用外,它也可以被当作堆栈.队列或双端队列进行操作.不是线程安全的,继 ...

  6. LInux ugo权限详解

    Linux 中的用户和组是用来控制使用者或者进程可以或者不可以使用哪些资源和硬件,是Linux权限控制最基本的方式. 用户和组可以看一下上一章的部分,先来看一下权限. 一.权限概览 在Linux下,使 ...

  7. C++ 虚函数 、纯虚函数、接口的实用方法和意义

    也许之前我很少写代码,更很少写面向对象的代码,即使有写多半也很容易写回到面向过程的老路上去.在写面向过程的代码的时候,根本不管什么函数重载和覆盖,想到要什么功能就变得法子的换个函数名字,心里想想:反正 ...

  8. S2_OOP第三章

    第一章 多态 概念 多态是具有表现多种型生态的能力的特征,同一个实现接口,使用不同的实例而执行不同的操作 子类转换父类(向上转型) 用父类接受子类,向上转型 向上转型的规则: 讲一个父类的引用志向一个 ...

  9. node.js上除了Express还有哪些好用的web开发框架

    老司机都有体会, 开发本身没有多难, 最纠结其实是最初的技术和框架选型, 本没有绝对的好坏之分, 可一旦选择了不适合于自己业务场景的框架, 将来木已成舟后开发和维护成本都很高, 等发现不合适的时候更换 ...

  10. 通过修改 LayoutInflater,全局替换字体!!!

    序 在 Android 下使用自定义字体已经是一个比较常见的需求了,最近也做了个比较深入的研究. 那么按照惯例我又要出个一篇有关 Android 修改字体相关的文章,但是写下来发现内容还挺多的,所以我 ...