【问题标题】:fgrep with string containing spaces inside ksh scriptfgrep,字符串在 ksh 脚本中包含空格
【发布时间】:2017-03-17 17:42:14
【问题描述】:

我正在尝试编写一个fgrep 语句,从文件中删除具有完整记录匹配的记录。我可以在命令行上执行此操作,但不能在 ksh 脚本中执行此操作。我使用的代码归结为以下 4 行代码:

   Header='abc def|ghi jkl' #I use the head command to populate this variable

   workfile=abc.txt

   command="fgrep -Fxv \'$Header\' $workfile" >$outfile

   $command

当我将$command 回显到 STDIN 时,该命令正是我在命令行上键入的内容(带单引号),并且在命令行上有效。当我在ksh 脚本(文件)中执行它时,似乎无法识别单引号,因为错误表明它正在解析空格。

我尝试过反引号、exec、eval、双引号而不是单引号,并且没有使用$command 变量。问题依然存在。

【问题讨论】:

  • 当人们试图构建一个变量$cmd 然后“运行它”时,通常是因为他们试图做一些 shell 可以通过不同路径解决的事情。你真的需要运行$command吗?我会将您的精力重新集中在让正常的 shell 脚本工作上,然后决定您是否真的需要这种额外的间接级别。您的$header 中的| 字符还不清楚。在 grep reg ex 中,它代表OR;在shell中,当然是pipe。所以这里有几个不清楚的问题。更新您的 Q(或针对此问题更改您的策略)。祝你好运。
  • 如果某个答案解决了您的问题,请点击旁边的大复选标记 (✓) 接受它,并可选择对其进行投票(投票至少需要 15 个声望点)。如果您发现其他答案有帮助,请给他们投票。接受和投票有助于未来的读者。请参阅the relevant help-center article。如果您的问题尚未完全得到解答,请提供反馈。如果您觉得自己找到了最佳解决方案,请将其发布为答案并自我接受。

标签: shell grep ksh


【解决方案1】:

Bash FAQ #50: I'm trying to put a command in a variable, but the complex cases always fail! 提供全面的指导 - 虽然它是为 Bash 编写的,但大部分也适用于 Ksh[1]

如果你想坚持将你的命令存储在一个变量中(定义一个函数是更好的选择),使用一个数组,它绕过了引用问题:

#!/usr/bin/env ksh

Header='abc def|ghi jkl'

workfile=abc.txt

# Store command and arguments as elements of an array
command=( 'fgrep' '-Fxv' "$Header" "$workfile" ) 

# Invoke the array as a command.
"${command[@]}" > "$outfile"

注意:只有simple命令可以存储在数组中,redirections不能是其中的一部分。


[1] 函数示例使用local 创建局部变量,ksh 不支持。省略local 来代替shell 全局变量,或者使用function <name> {...} 语法和typeset 而不是localksh 中声明局部变量。

【讨论】:

    【解决方案2】:

    我可以在命令行中执行此操作,但不能在 ksh 脚本中执行此操作

    这是一个使用 heredoc 的简单、便携、可靠的解决方案。

    #!/usr/bin/env ksh
    
    workfile=abc.txt
    outfile=out.txt  
    
    IFS= read -r Header <<'EOF'
    abc def|ghi jul
    EOF
    
    IFS= read -r command <<'EOF'
    grep -Fxv "$Header" "$workfile" > "$outfile"
    EOF
    
    eval "$command"
    

    解释

    (不能在上面的脚本中添加注释,因为它们会影响 heredoc 中的行)

    IFS= read -r Header <<'EOF'                   # Line separated literal strings
    abc def|ghi jul                               # Set into the $Header variable
    EOF                                           # As if it were a text file
    
    IFS= read -r command <<'EOF'                  # Command to execute
    grep -Fxv "$Header" "$workfile" > "$outfile"  # As if it were typed into
    EOF                                           # the shell command line
    
    eval "$command"                               # Execute the command
    

    上面的例子与有一个名为 header.txt 的文本文件相同,其中包含内容:abc def|ghi jul 并输入以下命令:

    • grep -Fxvf header.txt abc.txt

    heredoc 解决了脚本操作与命令行不同的问题 引用/expansions/escaping 问题。

    关于eval的注意事项:

    本例中eval 的使用是特定的。请参阅Eval command and security issues 了解eval 如何被滥用并导致潜在的非常破坏性结果的信息。


    更多细节/替代示例:

    为了完整、清晰和将此概念应用于其他情况的能力,关于 heredoc 的一些注释和替代演示:

    heredoc 在这个例子中的这个实现是专门按照以下标准设计的:

    • Literal 将内容字符串赋值给变量(使用'EOF'
    • 使用 eval 命令在 heredoc 本身内评估和执行引用的变量。

    文件还是heredoc?

    heredocgrep -F (fgrep) 结合使用的一个优点是能够将脚本的一部分视为文件.

    立案案例:

    • 您希望经常将“模式”行粘贴到文件中,并在必要时将其删除,无需修改脚本文件。

    heredoc 案例:

    • 您在特定文件已经存在的环境中应用脚本,并且您希望将特定的确切文字模式与其匹配。

    示例:

    • 场景:我有 5 个 VPS 服务器,我想要一个脚本来生成一个新的 fstab 文件,但要确保它不包含确切的行:
    • /dev/xvda1 / ext3 errors=remount-ro,noatime,barrier=0 0 1

    这种情况适合本问题中解决的情况类型。我可以在此答案中使用上述代码中的样板并将其修改如下:

    #!/usr/bin/env ksh
    
    workfile=/etc/fstab
    
    IFS= read -r Header <<'EOF'
    /dev/xvda1  /               ext3    errors=remount-ro,noatime,barrier=0 0       1
    EOF
    
    IFS= read -r command <<'EOF'
    grep -Fxv "$Header" "$workfile"
    EOF
    
    eval "$command"
    

    这会给我一个新的fstab 文件,而 heredoc 中不包含该行。

    【讨论】:

    • 您的 here-doc 解决方案强大的,但我仍然不会推广它,因为通常很难理解 eval 的全部含义,而且还有更好的非eval 解决方案可用。相比之下,您的第一个解决方案稳健,我鼓励您将其删除(例如,尝试使用 Header="a'b")。
    • eval $commandeval "$command"not 相同,尽管差异很少会出现:前者作为 多个 参数传递在前面完全进行外壳扩展,而后者作为单个参数传递,稍后(完全)由@扩展外壳987654338@。这是一个显示差异的示例命令:command='cd / &amp;&amp; echo *'
    • @hmedia1:感谢更新; ++。还请将-r 添加到您的读取命令中,以便\ 实例不会被“吃掉”(尝试read v &lt;&lt;&lt;'a\b'; echo "$v")。另外,我建议添加一个注释,虽然eval 被安全地使用在这种情况下,但通常应该避免。 (P.S.:你必须@-提及我才能收到回复通知)。
    • 要使read 命令可移植,您可以省略-d 选项,因为您正在阅读的只是一个单行。此外,不需要IFS=$'\n',但要读取标题行,您可能希望保留前导和尾随空格,请使用IFS= read -r Header ...,这将仅更改IFS对于该命令 .
    • 将参数传递给eval:你的IFS= eval $command 也很有趣。在这种情况下,它与eval "$command" 相同的原因是cd /; echo * 现在被调用shell 视为单个 词,即使尝试了通配,(除非set -f 生效),它碰巧不匹配任何东西,单词按原样传递。在 Bash 中,您可以通过在之前设置 shopt -s failglob 来验证是否仍然应用通配:IFS= eval $commandfail,而 eval "$command" 不会。一般来说,参数如何传递给 eval 并没有什么特别之处。
    猜你喜欢
    • 2014-03-05
    • 2012-08-01
    • 2012-05-30
    • 2017-09-11
    • 1970-01-01
    • 2017-12-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多