Bash陷阱

2020-09-08 07:17:28

或者,你知道,停止使用expr。通过使用参数展开,您可以执行expr所做的所有操作。上面的那个东西想做什么?是否删除单词的第一个字母?这可以在POSIX shell中使用PE或子字符串扩展来完成:说真的,除非您在Solaris上使用不符合POSIX的/bin/sh,否则没有理由使用expr。它是一个外部进程,所以它比进程内字符串操作慢得多。由于没有人使用它,也没有人理解它在做什么,所以您的代码很混乱,很难维护。通常:Unix UTF-8文本不使用BOM。纯文本的编码由区域设置、MIME类型或其他元数据确定。虽然BOM的存在通常不会损坏仅供人类阅读的UTF-8文档,但在任何旨在由自动化过程(如脚本、源代码、配置文件等)解释的文本文件中,BOM是有问题的(通常在语法上是非法的)。应将以BOM开头的文件视为与带有MS-DOS换行符的文件相同的外来文件。在外壳脚本中:在8位环境中透明使用UTF-8的情况下,使用物料清单会干扰任何需要在开头使用特定ASCII字符的协议或文件格式,例如在UNIX外壳脚本的开头使用的。';http://unicode.org/faq/utf_bom.html#bom5没有。这个表达式没有任何错误,但是您应该注意命令替换(所有形式:`...`、$(...)、$(<;file)、`<;file`和${0...;9}(Ksh))会删除所有尾随换行符。这通常是无关紧要的,甚至是可取的,但是如果您必须保留文字输出,包括任何可能的尾随换行符,这就变得很棘手,因为您无法知道输出中是否有它们,或者有多少。一种难看但有用的解决方法是在命令替换内添加后缀,并在外部删除它:另一种可移植性较差但可以说更漂亮的解决方案是使用带有空分隔符的read。#ksh(或启用lasttube的bash 4.2+)readlink-fn--";$dir_path";|IFS=读取-rd';';绝对目录路径。

此方法的缺点是,除非命令输出NUL字节,导致仅读取部分流,否则读取将始终返回FALSE。获取命令退出状态的唯一方法是通过PIPESTATUS。您还可以有意输出NUL字节以强制read返回true,并使用pipeail。Set-o pipeail{readlink-fn--";$dir_path";&;&;printf';\0';}|IFS=读取-rd';';绝对目录路径设置+o pipeail。

这在某种程度上造成了可移植性的混乱,因为Bash同时支持pipeail和PIPESTATUS,ksh93仅支持pipeail,并且只有最新版本的mksh支持pipeail,而早期版本仅支持PIPESTATUS。此外,需要最先进的ksh93版本才能在NUL字节停止读取。防止程序将传递给它们的文件名解释为选项的一种方法是使用路径名(请参阅上面的陷阱#3)。对于当前目录下的文件,名称可以使用相对路径名./作为前缀。但是,对于*.*这样的模式,可能会出现问题,因为它与./filename形式的字符串匹配。在一个简单的例子中,您可以直接使用GLOB来生成所需的匹配。但是,如果需要单独的模式匹配步骤(例如,结果已经过预处理并存储在数组中,需要过滤),则可以通过在模式中考虑前缀[[$file!=./*.*]]或从匹配中剥离模式来解决此问题。#bash shop-s nullglob for path in./*;do[[${path##*/}!=*.*]]&;&;rm";$path";为*中的文件执行#或更好的操作;为*;中的文件执行[[$file!=*.*]]&;&;rm";./$file";do#或更好的操作*.*;do rm";./$file";

另一种可能性是用--参数表示选项的结束。(同样,见#pf3)。在*;do[[$file!=*.*]]&;&;rm--";$file";中文件的shop-s nullglob已完成。

这是迄今为止最常见的涉及重定向的错误,通常是由希望将stdout和stderr同时定向到一个文件或管道的人执行的,他们会尝试这样做,但不理解为什么stderr仍然出现在他们的终端上。如果您对此感到困惑,那么您可能一开始就不了解重定向或可能的文件描述符是如何工作的。在执行命令之前,从左到右评估重定向。这个语义上不正确的代码实质上意味着:";首先将标准错误重定向到标准输出当前指向的位置(Tty),然后将标准输出重定向到日志文件";。这是倒退的。标准错误已经送到TTY了。改为使用以下内容:查看更深入的说明、复制描述符说明和BashGuide-重定向。$?只是请求

请勿导出CDPATH。在.bashrc中设置CDPATH不是问题,但是导出它会导致您运行的任何bash或sh脚本(碰巧使用cd)可能会更改行为。有两个问题。执行以下操作的脚本可能会将目录更改为~/myProject/ome/dir,而不是./ome/dir,具体取决于当时存在的目录。因此,CD可能会成功,并将脚本带到错误的目录,这可能会对以下命令产生有害影响,这些命令现在运行在与预期目录不同的目录中。第二个问题是,当cd在捕获输出的上下文中运行时:设置CDPATH时的副作用是,cd会将类似/home/user/ome/dir的内容输出到stdout,以指示它通过CDPATH找到了一个目录,这反过来将在输出变量中与某个命令的预期输出一起结束。脚本可以通过始终将./前缀到相对路径,或在脚本开始时运行unset的CDPATH,使其自身不受从环境继承的CDPATH的影响,但不要假设每个脚本编写者都已考虑到此缺陷,因此不要导出CDPATH。仅将变量的值直接赋给临时变量是不足以恢复其状态的。即使未设置初始变量,赋值也始终会产生已设置但为空的临时变量。对于IFS来说,这是一个特别的问题,因为空IFS与未设置的IFS具有完全不同的含义,将IFS设置为一个或两个命令的临时值是常见的要求。一种简单的解决方法是指定一个前缀来区分set和unset变量,然后在完成时将其删除。在可能的情况下,局部变量通常更可取。子壳是另一种可能性。用原始$(...)填充数组是不安全的。司令部代办。该命令的输出经历了单词拆分(在所有空格上,甚至在引号内的空格上),然后进行整形。如果有像*或嗯这样的词?或[abc],它将根据当前工作目录中的文件名展开。要选择替换项,您需要知道该命令是将其输出写入单行还是多行。如果它是单行:如果它是多行(并且您的目标是bash 4.0或更高版本):如果它是多行(并且您希望与bash 3.x兼容,或者希望您的命令的退出状态反映在读取操作的成功或失败中,而不依赖于仅在bash 4.4和更高版本中提供的行为):这将防止全局绑定。如果您需要避免在带引号的空格上拆分,它仍然帮不了您,但不幸的是,bash无法处理这种情况。对于通用的CSV(逗号分隔值)文件处理,您确实需要切换到具有专用CSV输入库的语言。GNU xargs支持并行运行多个作业。-P-n,其中n是要并行运行的作业数。序列100|xargs-N1-P10 ECHO";$a";|grep 5序列100|xargs-N1-P10 ECHO";$a";>;myoutput.txt。

这在许多情况下都可以很好地工作,但是有一个欺骗性的缺陷:如果$a包含超过8192个字符(限制取决于平台和版本),那么回显可能不是原子的(它可能被分成多个write()调用),并且存在两行混合的风险。$perl-e&39;print";a";x10000,";\n";>;foo$strace-e write bash-c';read-r foo<;foo;foo;ECHO";$foo";';>;/dev/NULL WRITE(1,";aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";...,8192)=8192Write(1,";aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";...,1809)=1809+exe。

显然,如果多次调用echo或printf:slowprint(){printf';start-%s';";$1";sleep";$1";printf';%s-end\n';";$1";}export-f slowprint seq 10|xargs-N1-i{}-P4 bash-c";slowprint{}&。#对比没有并行化的seq 10|xargs-n1-i{}bash-c";slowprint{}";#请务必看到下一个Pitfall中的警告!

并行作业的输出混合在一起,因为每个作业由两个(或更多)单独的write()调用组成。如果您需要未混合的输出,因此建议使用保证输出将被序列化的工具(例如GNU并行)。有关更多详细信息,请参阅混合问题演示。此命令包含CodeInjection漏洞。Find找到的文件名被注入shell命令并由sh解析。如果文件名包含外壳元字符,如;或$(#...),则可以通过`sh';将文件名作为代码执行。如果输入不能保证是整数,上一个Pitfall中的Slowprint&34;示例将是一个CodeInjection错误。更准确地说,POSIX find没有指定包含以下内容的参数

如果您的系统的strftime()不支持%s,您可以使用以下命令获取纪元时间:强制基数10解释仅适用于无符号数字。只要$i包含一个没有前导-或+的数字字符串,就可以了。但是,如果$i可能为负值,则此转换可能会失败,可能会有噪音(带有错误消息),或者更糟糕的是,会静默地失败(只是产生错误的结果)。