【问题标题】:How to avoid magic-numbers in shell?如何避免 shell 中的幻数?
【发布时间】:2018-01-13 06:08:57
【问题描述】:

我总是在我的交互式 shell 和 shell 脚本中写一些幻数。

例如,如果我想列出我的用户名和 shell,我会写

cut --delimiter=: --fields=1,7 /etc/passwd

存在两个幻数1,7。在其他情况下,幻数越来越多。

问题

如何避免交互式 shell 和 shell 脚本中的幻数?

补充背景

我们的老师告诉我们使用cut -d: -f1,7 /etc/passwd。但是对于新的linux用户,他们不知道df17是什么意思。(不只是新的linux用户,整个系统的配置文件太多了一个人记住每个幻数并不容易)

因此,在交互式 shell 中,我们可以使用 --delimiter--fields,并且 bash repl(或 zsh,fish) 具有良好的 tab 补全功能。

17 怎么样?在 shell 脚本中,在阅读 man 5 passwd 之后声明一些 const 变量如 LoginField=1 和 ShellField=7 是一个很好的方法。但是当有人在交互式shell中编写时,打开一个新窗口并搜索LoginField=1ShellField=7的常量并定义它并不是一个好主意。如何使用 tab 补全之类的东西来简化操作?

【问题讨论】:

  • 为什么不直接声明一些变量,比如USER_NAME_COLUMN=1BIN_FILE_COLUMN=2,然后最后在脚本中使用它:cut --delimiter=: --fields=$USER_NAME_COLUMN,$BIN_FILE_COLUMN /etc/passwd
  • 除了分配一些有意义的变量名之外,问题实际上是1, 7 是否实际上是固定的,或者您是否在询问是否可以从数据文件中以某种方式派生它们。您总是可以找到字段的数量,但您需要一些方法来告诉脚本使用哪个。您始终可以将它们作为参数传递给您的脚本。
  • 我们(以及 shell 文档)谈论交互式 shell。当你写REPL 时,没有人知道你在说什么,尽管这个词很笼统,但正确。

标签: linux bash shell unix magic-numbers


【解决方案1】:

man 5 passwd 的输出中获取人类可读的标题名称:

declare $(man 5 passwd | 
          sed -n '/^\s*·\s*/{s/^\s*·\s*//;y/ /_/;p}' | 
          sed -n 'p;=' | paste -d= - - )

请参阅下面的“它是如何工作的”了解它的作用,然后运行:

cut --delimiter=: \
    --fields=${login_name},${optional_user_command_interpreter} /etc/passwd

输出指定的/etc/passwd字段。


它是如何工作的。

描述/etc/passwdman 页面包含标题名称的项目符号列表。使用 GNU sed 找到项目符号 (·) 和前导空格,然后删除项目符号和空格,将剩余空格替换为下划线; sed第二个 实例提供新的行号,然后 paste 将标题名称添加到行号,= 介于两者之间:

man 5 passwd | 
sed -n '/^\s*·\s*/{s/^\s*·\s*//;y/ /_/;p}' | 
sed -n 'p;=' | paste -d= - -

输出:

login_name=1
optional_encrypted_password=2
numerical_user_ID=3
numerical_group_ID=4
user_name_or_comment_field=5
user_home_directory=6
optional_user_command_interpreter=7

declare 使它们在当前 shell 中处于活动状态。

【讨论】:

    【解决方案2】:

    解析 /etc/passwd 的经典方法是将每一列读入一个适当命名的变量:

    while IFS=: read name passwd uid gid gecos home shell _; do 
       ...
    done < /etc/passwd
    

    【讨论】:

      【解决方案3】:

      继续我的评论,很难确切地说出你在问什么。如果您只想给出有意义的变量名称,请按照其他答案中的说明进行操作。

      但是,如果您希望能够指定从命令行将哪些 字段 传递给 cut,那么您可以使用 位置参数 $1$2 将这些值传递到您的脚本中。

      您需要验证是否给出了两个输入并且都是整数。您可以通过一些简单的测试来做到这一点,例如

      #!/bin/bash
      
      [ -n "$1" ] && [ -n "$2" ] || { ## validate 2 parameters given
          printf "error: insufficient input\nusage: %s field1 field2\n" "${0##*/}"
          exit 1
      }
      
      ## validate both inputs are integer values
      [ "$1" -eq "$1" >/dev/null 2>&1 ] || {
          printf "error: field1 not integer value '%s'.\n" "$1"
          exit 1
      }
      
      [ "$2" -eq "$2" >/dev/null 2>&1 ] || {
          printf "error: field2 not integer value '%s'.\n" "$2"
          exit 1
      }
      
      cut --delimiter=: --fields=$1,$2 /etc/passwd
      

      使用/输出示例

      $ bash fields.sh
      error: insufficient input
      usage: fields.sh field1 field2
      
      $  bash fields.sh 1 d
      error: field2 not integer value 'd'.
      
      $  bash fields.sh 1 7
      root:/bin/bash
      bin:/usr/bin/nologin
      daemon:/usr/bin/nologin
      mail:/usr/bin/nologin
      ftp:/usr/bin/nologin
      http:/usr/bin/nologin
      uuidd:/usr/bin/nologin
      dbus:/usr/bin/nologin
      nobody:/usr/bin/nologin
      systemd-journal-gateway:/usr/bin/nologin
      systemd-timesync:/usr/bin/nologin
      systemd-network:/usr/bin/nologin
      systemd-bus-proxy:/usr/bin/nologin
      <snip>
      

      或者,如果您选择查看字段 13,那么您只需将它们作为参数传递,例如

      $  bash fields.sh 1 3
      root:0
      bin:1
      daemon:2
      mail:8
      ftp:14
      http:33
      uuidd:68
      dbus:81
      nobody:99
      systemd-journal-gateway:191
      systemd-timesync:192
      systemd-network:193
      systemd-bus-proxy:194
      <snip>
      

      检查一下,如果您还有其他问题,请告诉我。

      【讨论】:

      • 对不起,我的表达能力很差。我们的老师告诉我们使用cut -d: -f1,7 /etc/passwd。但是对于新的 linux 用户,他们不知道 d,f,1,7 是什么意思。所以,在 shell REPL 中,我们可以使用 --delimiter--fields17 怎么样?
      • 17 字段只对应于存储在/etc/password 中的Linux username 和默认shell 值。在上面的示例中,当我为字段传递13 时,从文件中提取了usernameUID17 只需告诉 cut 获取文件中 1st7th 冒号 (':') 之前的内容。在我的示例中,$1 保存第一个命令行参数,$2 保存第二个。如果您愿意,可以将它们重新分配给更有意义的名称,但对于小型脚本,您也可以直接使用它们。
      • 没问题,如果您要查找/替换值1,7,那么这就是将它们作为参数传递的全部目的。您在 shell 脚本中没有制表符补全(除非您编写它),但您可以提供对 -h--help 的正常响应,或者如果用户不输入任何内容或输入不正确的值,则提供 usage: 信息,因为我上面做了。如果你只是简单地运行我命名为fields.sh 的脚本而不带参数,它会告诉你需要什么,例如"usage: fields.sh field1 field2"。您可以添加您喜欢的任何其他描述性文本并检查-h--help
      【解决方案4】:

      使用导出: export field_param="1,7" (你可以把它放在 .bashrc 文件中,以便在每次启动 shell 会话时进行配置)。此导出可以是 .sh 脚本的一部分。将它们放在文件的头部/顶部是一个好习惯。 然后: cut --delimiter=: --fields=$field_param /etc/passwd 这样,您将需要在唯一的位置编辑幻数。

      【讨论】:

        【解决方案5】:

        就像在其他语言中一样 - 通过使用变量。示例:

        $ username_column=1
        $ shell_column=7 
        $ cut --delimiter=: --fields="$username_column","$shell_column" /etc/passwd
        

        变量可以定义在脚本的顶部,这样就可以 易于修改,或者可以在外部类似配置的文件中设置 由多个脚本共享。

        【讨论】:

        • 如果这就是REPL 的意思,上述内容也适用于交互式 shell。直接在终端中运行这些命令,它的工作方式与在脚本中相同。
        【解决方案6】:

        使用变量:

        LoginField=1 ShellField=7
        cut --delimiter=: --fields="$LoginField,$ShellField" /etc/passwd
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-10-24
          • 2011-04-10
          • 1970-01-01
          • 2023-03-30
          • 1970-01-01
          • 1970-01-01
          • 2011-01-01
          相关资源
          最近更新 更多