【问题标题】:Extract all text between two directory entries in a file提取文件中两个目录条目之间的所有文本
【发布时间】:2017-08-09 20:19:46
【问题描述】:

我有一个文件记录了几个目录的结构。我正在尝试单独打印每个目录的文本。我的输入文件如下所示:

$ cat file.txt
/bin:
file_1
file_2
file_3

/sbin:
file_a
file_b
file_c

/usr/local/bin:
doc_a
doc_b
doc_c

我要做的是根据用户选择打印文件的特定部分:

#!/bin/bash

PS3=$'\nMake a selection '
select dir in $(grep ':' file.txt;) do
    case $REPLY in
        [0-9]) echo $dir
               # Need something here. Maybe a pcregrep regex?
               # pcregrep '(<= $dir)*(some_fancy_regex)' file.txt
               break;;
    esac
done

向用户显示菜单选项:

1) /bin:
2) /sbin:
3) /usr/local/bin:

Make a selection

假设用户选择 2。目前,这只是在屏幕上打印所选目录。我想显示目录以及它包含的文件。

/sbin:
file_a
file_b
file_c

根据我的阅读,pcre 正则表达式似乎可以在这里工作。我几乎不了解非 pcre 风格的正则表达式。我试图让我的大脑围绕积极和消极的前瞻和后视,但我真的不知道我在做什么。如果有人可以帮助我解决这个问题,我将不胜感激。

  1. 所有目录都以/ 开头并以: 结尾
  2. 每个目录下列出的文件名可能包含:
    • [a-z], [A-Z],[0-9]
    • 文字字符._-[
  3. 所有目录/文件结构都以空行结束

【问题讨论】:

  • 这个问题有很多非常好的答案。很难说是哪一个“解决”了问题,因为在这一点上,所有的答案都做到了。
  • 只有one of them 不涉及shell 循环,所以why-is-using-a-shell-loop-to-process-text-considered-bad-practice 可能会有所帮助。同样,这也是唯一适用于输入文件的所有内容的,并且可以在所有 UNIX 系统上的所有 bourne 派生的 shell 之间移植。其他的只能在特定的 shell 中工作和/或当您的输入包含正则表达式元字符或转义字符或....时会失败。

标签: regex bash macos grep pcre


【解决方案1】:

使用 GNU sed 和 bash:

dir="/usr/local/bin:"
sed -n "/${dir//\//\/}/,/^$/{/^$/d;p}" file

使用 bash:

dir="/usr/local/bin:"
while IFS= read -r line; do
  [[ $line == $dir ]] && switch=1
  [[ $line == "" ]] && switch=0
  [[ $switch == 1 ]] && echo "$line"
done < file

两种情况下的输出:

/usr/local/bin: doc_a doc_b doc_c

【讨论】:

    【解决方案2】:

    Grep 不是最好的工具,因为它是面向行的;除了some contortion-z POSIX 未指定选项外,您不能真正让 grep 查看跨越多行的表达式。

    你可以这样做:

    #!/bin/bash
    
    PS3=$'\nMake a selection '
    
    mapfile -t opts < <(grep ':' file.txt)
    
    select dir in "${opts[@]}"; do
        sed -n "\@$dir@,/^$/{/^$/q;p}" file.txt
        break
    done
    

    首先,我更改了您的菜单创建。请注意,命令替换中有一个备用分号,后面缺少一个;如果目录名称中有空格,像这样使用 grep 也会中断。因此,我使用mapfile 将所有包含: 的行放入一个数组中。

    然后,一旦我知道了目录,我就使用 sed 打印“从目录名称开始直到下一个空行”。那只是

    sed -n "/$dir/,/^$/p"
    

    但这在多个方面都存在不足。首先,目录名称可以包含斜杠,这会使/ 定界地址出错。我们可以用\%regexp%代替/regexp/,其中%可以是任意字符;我选择了@

    现在,我们有

    sed -n "\@$dir@,/^$/p"
    

    差不多了,但打印尾随空白行;我们通过使用{/^$/q;p} 而不是仅使用p 来抑制这种情况,它表示“如果该行为空白,则退出,否则打印它”。

    示例输出(编辑为使用带空格的目录名称):

    1) /bin blah:
    2) /sbin:
    3) /usr/local/bin:
    
    Make a selection 1
    /bin blah:
    file_1
    file_2
    file_3
    

    备注:非 GNU seds(如 macOS 中的那个)可能会抱怨花括号中的两个命令;改用{/^$/q;p;}(额外的分号)可能会有所帮助。

    【讨论】:

      【解决方案3】:

      它可以在 bash 4 中以 单程 完成,无需使用任何外部工具。下面是解决这个问题的脚本:

      #!/bin/bash
      
      # declare an associative array
      declare -A dirs=()
      
      # loop thru all lines and populate our associate array
      # with dir as key and \n separated file names as value
      while read -r; do
         [[ -z $REPLY ]] && continue
         if [[ $REPLY == *: ]]; then
            d="$REPLY"
         else
            dirs["$d"]+=$'\n'"$REPLY"
         fi
      done < file.txt
      
      # present a menu to customer and print selected dir name with file names
      select dir in "${!dirs[@]}"; do
         if [[ -n $dir ]]; then
            printf '%s%s\n' "$dir" "${dirs[$dir]}"
            break
         fi
      done
      

      输出:

      1) /usr/local/bin:
      2) /bin:
      3) /sbin:
      #? 1
      /usr/local/bin:
      doc_a
      doc_b
      doc_c
      

      还有这个:

      1) /usr/local/bin:
      2) /bin:
      3) /sbin:
      #? 3
      /sbin:
      file_a
      file_b
      file_c
      

      【讨论】:

      • select 循环中添加了健全性检查,以确保选择了有效的菜单项。
      • select 将在用户输入无效选项时将空值放入dir 变量中。如果我们不使用上述条件进行检查,则 break 将停止选择循环。
      • 我想我基本上明白了大部分。但是你能解释一下dirs["$d"]+=$'\n'"$REPLY"这行吗?
      • dirs["$d"]+=$'\n'"$REPLY"newline + current line 附加到数组条目中,键为 $d(我们将 d 设置为前面以冒号结尾的行)我们使用$'\n' 表示法在每个条目前加上换行符。
      【解决方案4】:

      不要将 shell 误认为是文本处理工具,这就是 awk 的用途。您只需要这 4 行代码:

      $ cat tst.sh
      awk -v RS= -F'\n' -v OFS=') ' '{print NR, $1}' file.txt >&2
      printf '\nMake a selection: ' >&2
      IFS= read -r rsp
      awk -v RS= -v nr="$rsp" 'NR==nr' file.txt
      
      $ ./tst.sh
      1) /bin:
      2) /sbin:
      3) /usr/local/bin:
      
      Make a selection: 2
      /sbin:
      file_a
      file_b
      file_c
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-02-03
        • 1970-01-01
        • 2015-07-08
        • 1970-01-01
        • 1970-01-01
        • 2023-01-04
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多