“xargs -I s printf s”是否比“xargs -n 1 printf”更兼容?

tai*_*ear 3 compatibility shell-script posix

概括

xargs -I s printf s不是更兼容xargs -n 1 printf

背景

处理可能包含 0x00 的二进制数据。我知道如何将二进制数据转换为文本,如下所示:

# make sure that you have done this: export LC_ALL=C
od -A n -t x1 -v | # or -t o1 or -t u1 or whatever
tr ABCDEF abcdef | # because POSIX doesn't specify in which case
tr -d ' \t\n' | # because POSIX says they are delimiters
fold -w 2 |
grep . # to make sure to terminate final line with LF
Run Code Online (Sandbox Code Playgroud)

...这里是如何转换回二进制:

# input: for each line, /^[0-9a-f]\{2\}$/
# also make sure export LC_ALL=C before
awk -v _maxlen="$(getconf ARG_MAX 2>/dev/null)" '
  BEGIN{
    # (1) make a table
    # assume that every non-null byte can be converted easily
    # actually not portable in Termux; LC_ALL=C does not work and
    # awk is gawk by default, which depends on locale.
    # to deal with it, here is alternative:
    # for(i=0;i<256;i++){
    #   xc[sprintf("%02x",i)]=sprintf("\\\\%03o",i);
    #   xl[sprintf("%02x",i)]=5;
    # }
    # # and skip to (2)
    # but why not just env -i awk to force one true awk, if so.
    # also is not it pretty rare that C locale is not available?
    for(i=1;i<256;i++){
      xc[sprintf("%02x",i)]=sprintf("%c",i);
      xl[sprintf("%02x",i)]=1;
    }
    
    # now for chars that requires special converting.
    
    # numbers; for previous char is \\ooo.
    for(i=48;i<58;i++){
      xc[sprintf("%02x",i)]=sprintf("\\\\%03o",i);
      xl[sprintf("%02x",i)]=5;
    }
    
    # and what cannot be easily passed to xargs -n 1 printf
    
    # null
    xc["00"]="\\\\000"; xl["00"]=5;
    
    # <space>
    xc["09"]="\\\\t";   xl["09"]=3;
    xc["0a"]="\\\\n";   xl["0a"]=3;
    xc["0b"]="\\\\v";   xl["0b"]=3;
    xc["0c"]="\\\\f";   xl["0c"]=3;
    xc["0d"]="\\\\r";   xl["0d"]=3;
    xc["20"]="\\\\040"; xl["20"]=5;
    
    # meta chars for printf
    xc["25"]="%%";      xl["25"]=2;
    xc["5c"]="\\\\\\\\";xl["5c"]=4;
    
    # hyphen; to prevent to be treated as if it were an option
    xc["2d"]="\\\\055"; xl["2d"]=5;
    
    # chars for quotation
    xc["22"]="\\\"";    xl["22"]=2;
    xc["27"]="\\'\''";  xl["27"]=2;
    
    # (2) preparation
    
    # reason why 4096: _POSIX_ARG_MAX
    # reason why length("printf "): because of ARG_MAX
    # reason why 4096/2 and _maxlen/2: because some xargs such as GNU specifies buffer length less than ARG_MAX
    if(_maxlen==""){
      maxlen=(4096/2)-length("printf ");
    }else{
      maxlen=int(_maxlen/2)-length("printf ");
    }
    
    ORS=""; LF=sprintf("\n");
    arglen=0;
  }
  {
    # (3) actual conversion here.
    
    # XXX. not sure why arglen+4>maxlen.
    # but I think maximum value for xl[$0] is 5.
    # and maybe final LF is 1.
    if(arglen+4>maxlen){
      print LF;
      arglen=0;
    }
    print xc[$0];
    arglen+=xl[$0];
  }
  END{
    # for some xargs who hates input w/o LF termination
    if(NR>0)print LF;
  }
' |
xargs -n 1 printf
Run Code Online (Sandbox Code Playgroud)

我发现空输入的问题:在 GNU/Linux 中,它失败了,如下所示:

$ xargs -n 1 printf </dev/null
printf: missing operand
Try 'printf --help' for more information.
Run Code Online (Sandbox Code Playgroud)

然后我发现xargs -n 1 printf 2>/dev/null || :if(NR==0)printf"\"\"\n";END块上添加,并且xargs -I s printf s是替代方案。我只看到第一个实际用于 ShellShoccar-jpn 的程序,但我认为它有点强大。第二个也没有最后一个干净。第三个不仅可以作为 GNU/Linux 的替代方案,还可以作为其他(或大多数其他)环境的替代方案吗?因为我只有 GNU/Linux,所以我不知道如何在所有其他环境中验证我的想法。最简单的方法是获取它们的来源并参考它们,或者参考它们的手册。如果根本无法验证,那么我必须放弃。

我的知识

  • printf正如 POSIX 所说,这似乎至少需要一个论点。
  • 有些在xargs没有 LF 终止的情况下忽略输入;grep ^ | xargs something herexargs something here可能没有 LF 终止的输入更便携。
  • xargs 对于没有非空行的输入是不可移植的;printf ' \n\n' | xargs echo foo在 FreeBSD 和fooGNU/Linux 上不输出任何内容。在这种情况下,您必须使 xargs 的命令对于此类输入是安全的,或者让命令忽略错误。
  • FreeBSD 的 xargs 接收它的参数就好像它们一样,$@而 GNU/Linux 的好像它们是"$@".
  • 通过反斜杠转义适用于 xargs,比如作为输出printf '\\\\\\'"'" | sed "$(printf 's/[\047\042\\]/\\\\&/g')" | xargs printf获取\'

聚苯乙烯

我发现这xargs -E ''比没有选项更兼容,因为某些 xargs 默认值-E _

Sté*_*las 6

xargs就可移植性(和界面设计)而言,可能是最糟糕的 POSIX 实用程序。我会远离它。怎么样:

<file.hex awk -v q="'" -v ORS= '
  BEGIN{
    for (i=0; i<256; i++) c[sprintf("%02x", i)] = sprintf("\\%o", i)
  }
  NR % 50 == 1 {print sep"printf "q; sep = q"\n"}
  {print c[$0]}
  END {if (sep) print q"\n"}
' | sh
Run Code Online (Sandbox Code Playgroud)

而不是例如?

awk部分输出如下内容:

printf '\61\12\62\12\63\12\64\12\65\12\66\12\67\12\70\12\71\12\61\60\12\61\61\12\61\62\12\61\63\12\61\64\12\61\65\12\61\66\12\61\67\12\61\70\12\61\71\12\62\60'
printf '\12\62\61\12\62\62\12\62\63\12\62\64\12\62\65\12\62\66\12\62\67\12\62\70\12\62\71\12\63\60\12\63\61\12\63\62\12\63\63\12\63\64\12\63\65\12\63\66\12\63'
printf '\67\12\63\70\12\63\71\12\64\60\12\64\61\12\64\62\12\64\63\12\64\64\12\64\65\12\64\66\12\64\67\12\64\70\12\64\71\12\65\60\12\65\61\12\65\62\12\65\63\12'
printf '\65\64\12\65\65\12\65\66\12\65\67\12\65\70\12\65\71\12\66\60\12\66\61\12\66\62\12\66\63\12\66\64\12\66\65\12\66\66\12\66\67\12\66\70\12\66\71\12\67\60'
printf '\12\67\61\12\67\62\12\67\63\12\67\64\12\67\65\12\67\66\12\67\67\12\67\70\12\67\71\12\70\60\12\70\61\12\70\62\12\70\63\12\70\64\12\70\65\12\70\66\12\70'
printf '\67\12\70\70\12\70\71\12\71\60\12\71\61\12\71\62\12\71\63\12\71\64\12\71\65\12\71\66\12\71\67\12\71\70\12\71\71\12\61\60\60\12'
Run Code Online (Sandbox Code Playgroud)

sh解读。在内置的sh实现中printf,这不会分叉额外的进程。在那些不是的情况下,这些行应该足够短以避免 ARG_MAX 限制,但printf每 50 个字节运行的行数仍不超过 1个。

请注意,您无法仅根据 ARG_MAX 的值真正确定命令行的最大长度。如何达到和处理该限制在很大程度上取决于系统及其版本。在许多情况下,ARG_MAX 受到累积大小的限制,包括指针argv[]envp[]列表(因此在 64 位系统上,每个参数/环境变量通常为 8 个字节)加上每个 arg/env 字符串的字节大小(包括 NUL 分隔符)。Linux 对单个参数的大小也有独立的限制。

还要注意的是替换\12\n,例如仅在基于ASCII的系统有效。POSIX 不指定字符的编码(NUL 除外)。仍然有 POSIX 系统使用 EBCDIC 的一些变体而不是 ASCII。