【问题标题】:Splitting /proc/cmdline arguments with spaces用空格分割 /proc/cmdline 参数
【发布时间】:2010-11-02 20:50:13
【问题描述】:

大多数解析 /proc/cmdline 的脚本将其分解为单词,然后使用 case 语句过滤掉参数,例如:

CMDLINE="quiet union=aufs wlan=FOO"
for x in $CMDLINE
do
»···case $x in
»···»···wlan=*)
»···»···echo "${x//wlan=}"
»···»···;;
»···esac
done

问题在于 WLAN ESSID 有空格。用户期望设置wlan='FOO BAR'(类似于 shell 变量),然后使用上面的代码得到 'FOO 的意外结果,因为 for 循环在空格上拆分。

有没有更好的方法从几乎无法评估它的 shell 脚本中解析 /proc/cmdline

或者有一些引用技巧吗?我在想我也许可以要求用户实体引用空格并像这样解码:/bin/busybox httpd -d "FOO%20BAR"。或者这是一个糟糕的解决方案?

【问题讨论】:

  • 您的 shell 已经在解析命令行,拆分参数。为什么要“解析”它,而不仅仅是假设用户会正确引用参数——就像它与任何其他命令有关?如果要删除带有空格的文件,请键入 «rm "file with space"»。如果您的 ESSID 是 «essid wlan=FOO» 怎么办?
  • 好吧,我犯了和 JesperE 一样的错误……

标签: parsing shell word argument-passing split


【解决方案1】:

有一些方法:

  1. cat /proc/PID/cmdline | tr '\000' ' '

  2. cat /proc/PID/cmdline | xargs -0 echo

这些适用于大多数情况,但当参数中有空格时会失败。不过我确实认为有比使用/proc/PID/cmdline 更好的方法。

【讨论】:

  • 更具体地说,/proc/cmdline 是一些引导加载程序(GrUB、LiLo、SysLinux 等)提供给内核的命令行。 /proc/$PID/cmdline 是由每个进程的 execve() 系统调用处理的命令行。
  • 这是一个比最受好评的解决方案更好的解决方案:简明扼要(使用标准工具将 '\000' 替换为空格)
  • 在 Python 3 中:cmdline = open('/proc/PID/cmdline', 'rb').read().replace(b'\0', b' ').decode()
  • 这是最合适的答案。可以进一步缩短tr '\000' ' ' < /proc/${PID}/cmdline
  • 这实际上是错误的答案,因为问题是关于 /proc/cmdline,它的语法与 /proc/*PID*/cmdline 不同...
【解决方案2】:
set -- $(cat /proc/cmdline)
for x in "$@"; do
    case "$x" in
        wlan=*)
        echo "${x#wlan=}"
        ;;
    esac
done

【讨论】:

  • 非常优雅的解决方案!
  • for x; do ... 默认为与for x in "$@"; do ... 相同的语义,但更明确的形式是......更明确。只是一个旁注。 :)
【解决方案3】:

最常见的是,\0ctal 转义序列用于不可接受的空格。

在 Bash 中,printf 可用于取消转义,例如

CMDLINE='quiet union=aufs wlan=FOO\040BAR'
for x in $CMDLINE; do
    [[ $x = wlan=* ]] || continue
   printf '%b\n' "${x#wlan=}"
done

【讨论】:

  • 我更喜欢 Web 风格的实体引用。
  • 八进制转义在 UNIX 环境中更为常见(和传统)。例如,这是在 /etc/fstab 中为挂载路径添加空格的方法。
  • 由于这是一个“Web 产品”Webconverger 的参数,其他参数如主页webconverger.org/boot 也将进行 URL 编码,我认为我的初始选择是最好的。
  • 那么你必须使用某种文本处理工具:sed/perl 或像 new bash 这样的好的 shell。
  • 好吧,您可能可以使用十六进制转义符,它至少具有与 URL 转义符相同的数字。该示例将变为wlan=FOO\x20BAR。我认为 printf 仍然会取消它。
【解决方案4】:

由于您希望 shell 解析 /proc/cmdline 内容,因此很难避免对其进行评估。

#!/bin/bash
eval "kernel_args=( $(cat /proc/cmdline) )"
for arg in "${kernel_args[@]}" ; do
    case "${arg}" in
        wlan=*)
            echo "${arg#wlan=}"
            ;;
    esac
done

这显然很危险,因为它会盲目地运行内核命令行上指定的任何内容,例如quiet union=aufs wlan=FOO ) ; touch EVIL ; q=( q

转义空格 (\x20) 听起来是最直接、最安全的方法。

一个重要的替代方法是使用一些解析器,它可以理解类似于 shell 的语法。 在这种情况下,您甚至可能不再需要 shell。 以python为例:

$ cat /proc/cmdline
quiet union=aufs wlan='FOO BAR' key="val with space" ) ; touch EVIL ; q=( q
$ python -c 'import shlex; print shlex.split(None)' < /proc/cmdline
['quiet', 'union=aufs', 'wlan=FOO BAR', 'key=val with space', ')', ';', 'touch', 'EVIL', ';', 'q=(', 'q']

【讨论】:

    【解决方案5】:

    使用xargs -n1:

    [centos@centos7 ~]$ CMDLINE="quiet union=aufs wlan='FOO BAR'"
    [centos@centos7 ~]$ echo $CMDLINE
    quiet union=aufs wlan='FOO BAR'
    [centos@centos7 ~]$ echo $CMDLINE | xargs -n1
    quiet
    union=aufs
    wlan=FOO BAR
    
    [centos@centos7 ~]$ xargs -n1 -a /proc/cmdline
    BOOT_IMAGE=/boot/vmlinuz-3.10.0-862.14.4.el7.x86_64
    root=UUID=3260cdba-e07e-408f-93b3-c4e9ff55ab10
    ro
    consoleblank=0
    crashkernel=auto
    rhgb
    quiet
    LANG=en_US.UTF-8
    

    【讨论】:

    • 这是完美的解决方案;每行都有这些,后处理非常容易。
    • 我已经阅读了手册页,但仍然不明白 -n1 的作用以及它为什么起作用。但它确实有效,我喜欢它。
    【解决方案6】:

    您可以使用 bash 执行以下操作,这会将这些参数转换为 $cmdline_union 和 $cmdline_wlan 等变量:

    bash -c "for i in $(cat /proc/cmdline); do printf \"cmdline_%q\n\" \"\$i\"; done" | grep = > /tmp/cmdline.sh
    . /tmp/cmdline.sh
    

    然后你会像在普通 shell 中一样引用和/或转义内容。

    【讨论】:

      【解决方案7】:

      豪华:

      $ f() { echo $1 - $3 - $2 - $4 
      > }
      $ a="quiet union=aufs wlan=FOO"
      $ f $a
      quiet - wlan=FOO - union=aufs -
      

      您可以定义一个函数并将您的 $CMDLINE unquoted 作为该函数的参数。然后您将调用 shell 的解析机制。请注意,您应该在将要运行的 shell 上对此进行测试——zsh 通过引用做了一些有趣的事情 ;-)。

      然后你可以告诉用户像在 shell 中那样做引用:

      #!/bin/posh
      CMDLINE="quiet union=aufs wlan=FOO"
      f() {
              while test x"$1" != x 
              do      
                      case $1 in
                              union=*)        echo ${1##union=}; shift;;
                              *)              shift;; 
                      esac    
              done    
      }       
      f $CMDLINE
      

      (posh - 符合策略的普通 SHell,一个剥离了标准 POSIX 之外的任何功能的 shell)

      【讨论】:

      • 因为您可以吃更多的论据(2 美元、3 美元……以及更多的轮班)。我同意这对 x=y 参数没有那么有用; -x y 更多​​。但是这个解决方案更通用,也更惯用(在 shell 脚本中经常看到它)。
      • 那么用户如何引用像'foo bar'这样的essid? wlan=foo\040bar 像其他答案一样吗?老实说,我认为 URL 编码“%20”对于普通用户来说比 shell 八进制编码更容易。
      • 在这种情况下,将应用类似于 shell 的转义:CMDLINE="quiet wlan='foo bar bar union=of=consumers and-yet-another word' union=aufs"。但是......现在我认为如果这也意味着内核参数,你应该宁愿使用八进制。内核不做外壳扩展,看到上面的 wlan id 可能会感到困惑。
      【解决方案8】:

      发现here 是一种使用 awk 的好方法,不幸的是它只能使用双引号:

      # Replace spaces outside double quotes with newlines
      args=`cat /proc/cmdline | tr -d '\n' | awk 'BEGIN {RS="\"";ORS="\"" }{if (NR%2==1){gsub(/ /,"\n",$0);print $0} else {print $0}}'`
      
      IFS='                                                                           
      '                                                                               
      for line in $args; do                                                           
           key=${line%%=*}                                                            
           value=${line#*=}                                                           
      
           value=`echo $value | sed -e 's/^"//' -e 's/"$//'`                          
      
           printf "%20s = %s\n" "$key" "$value"                                       
      
      done
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-06-20
        • 1970-01-01
        • 2012-11-12
        • 2012-09-22
        相关资源
        最近更新 更多