set2 的历史 Unix V5 tr 命令填充行为与我们今天认为的“经典”System V(1983-1988)行为不同吗?

5 history tr

tr司令部已有近 40 年的历史。它似乎第一次出现在 Unix 中是在 1973 年的Unix V4 中。这个的来源不可用。大约 6 个月后的 1974 年 6 月,这可能是Unix V5命令的第二个可用的 Unix 实现:

int dflag 0;
int sflag 0;
int cflag 0;
int save 0;
char code[256];
char squeez[256];
char vect[256];
struct string { int last, max, rep; char *p; } string1, string2;
int inbuf[259];

main(argc,argv)
char **argv;
{
    int i, j;
    int c, d;
    char *compl;
    extern fout;

    string1.last = string2.last = 0;
    string1.max = string2.max = 0;
    string1.rep = string2.rep = 0;
    string1.p = string2.p = "";

    if(--argc>0) {
        argv++;
        if(*argv[0]=='-'&&argv[0][4]!=0) {
            while(*++argv[0])
                switch(*argv[0]) {
                case 'c':
                    cflag++;
                    continue;
                case 'd':
                    dflag++;
                    continue;
                case 's':
                    sflag++;
                    continue;
                }
            argc--;
            argv++;
        }
    }
    if(argc>0) string1.p = argv[0];
    if(argc>1) string2.p = argv[1];
    for(i=0; i<256; i++)
        code[i] = vect[i] = 0;
    if(cflag) {
        while(c = next(&string1))
            vect[c&0377] = 1;
        j = 0;
        for(i=1; i<256; i++)
            if(vect[i]==0) vect[j++] = i;
        vect[j] = 0;
        compl = vect;
    }
    for(i=0; i<256; i++)
        squeez[i] = 0;
    for(;;){
        if(cflag) c = *compl++;
        else c = next(&string1);
        if(c==0) break;
        d = next(&string2);
        if(d==0) d = c;
        code[c&0377] = d;
        squeez[d&0377] = 1;
    }
    while(d = next(&string2))
        squeez[d&0377] = 1;
    squeez[0] = 1;
    for(i=0;i<256;i++) {
        if(code[i]==0) code[i] = i;
        else if(dflag) code[i] = 0;
    }

    inbuf[0] = 0;
    fout = dup(1);
    close(1);
    while((c=getc(inbuf)) >=0 ) {
        if(c == 0) continue;
        if(c = code[c&0377]&0377)
            if(!sflag || c!=save || !squeez[c&0377])
                putchar(save = c);
    }
    flush();
}

next(s)
struct string *s;
{
    int a, b, c, n;
    int base;

    if(--s->rep > 0) return(s->last);
    if(s->last < s->max) return(++s->last);
    if(*s->p=='[') {
        nextc(s);
        s->last = a = nextc(s);
        s->max = 0;
        switch(nextc(s)) {
        case '-':
            b = nextc(s);
            if(b<a || *s->p++!=']')
                goto error;
            s->max = b;
            return(a);
        case '*':
            base = (*s->p=='0')?8:10;
            n = 0;
            while((c = *s->p)>='0' && c<'0'+base) {
                n = base*n + c - '0';
                s->p++;
            }
            if(*s->p++!=']') goto error;
            if(n==0) n = 1000;
            s->rep = n;
            return(a);
        default:
        error:
            write(1,"Bad string\n",11);
            exit();
        }
    }
    return(nextc(s));
}

nextc(s)
struct string *s;
{
    int c, i, n;

    c = *s->p++;
    if(c=='\\') {
        i = n = 0;
        while(i<3 && (c = *s->p)>='0' && c<='7') {
            n = n*8 + c - '0';
            i++;
            s->p++;
        }
        if(i>0) c = n;
        else c = *s->p++;
    }
    if(c==0) *--s->p = 0;
    return(c&0377);
}
Run Code Online (Sandbox Code Playgroud)

随着时间的推移,从早期开始,命令处理不同长度的集合的方式发生了变化,这里我的兴趣是 set2 比 set1 短的情况。

GNU Coreutils的 手册讨论的情况:

当 tr 执行翻译时,set1 和 set2 通常具有相同的长度。如果 set1 比 set2 短,则 set2 末尾的多余字符将被忽略。

另一方面,使 set1 长于 set2 是不可移植的;POSIX 表示结果未定义。在这种情况下,BSD tr 通过根据需要多次重复 set2 的最后一个字符来填充 set2 到 set1 的长度。System V tr 将 set1 截断为 set2 的长度。

默认情况下,GNU tr 像 BSD tr 一样处理这种情况。当给出 --truncate-set1 (-t) 选项时,GNU tr 会像 System V tr > 那样处理这种情况。对于翻译以外的操作,将忽略此选项。

在这种情况下,像 System V tr 一样打破了相对常见的 BSD 习惯用法:

 tr -cs A-Za-z0-9 '\012'
Run Code Online (Sandbox Code Playgroud)

因为它只将零字节(set1 的补码中的第一个元素)而不是所有非字母数字转换为换行符。

The Open Group Base Specifications Issue 7 IEEE Std 1003.1 2013 版中也有这样的讨论:

当 string2 比 string1 短时,历史系统 V 和 BSD 系统之间会产生差异。BSD 系统用在 string2 中找到的最后一个字符填充 string2。因此,可以执行以下操作:

tr 0123456789 d

这会将所有数字转换为字母“d”。由于这一领域在 POSIX.1-2008 的这一卷中没有明确指定,因此 BSD 和 System V 行为都是允许的,但符合要求的应用程序不能依赖于 BSD 行为。它必须按以下方式对示例进行编码:

tr 0123456789 '[d*]'

现在,如果您阅读V4V5 中tr 命令的手册页,您会在两者中看到以下参考:

If string2 is short, it is padded with corresponding characters from string1.
Run Code Online (Sandbox Code Playgroud)

但是在 V6手册和后来的 Unix 早期版本中省略了该引用,但该命令的 V6 实现是否与 V5 相同?所以你在手册上有区别,但在代码上没有?此外,这个实现似乎不同于所谓的“经典 BSD 或 System V”行为,即填充从 set2 元素添加或截断到 set1 的长度。

那么 V4-V5 的实现与 System V 里程碑 Unix 有什么不同,这种不同实现的基本原理是什么,最终为什么它会被抛弃?我如何才能找到有关命令的这种早期设计的更多信息?