提取文件

时间:2017-03-18 07:23:44

标签: regex bash macos grep pcre

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

$ 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. 结尾
  3. 每个目录下列出的文件名可能包含:
    • [a-z][A-Z][0-9]
    • 文字字符. _ - [
  4. 所有目录/文件结构都以空白行结束

4 个答案:

答案 0 :(得分: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

答案 1 :(得分:1)

Grep不是最好的工具,因为它是面向行的;除了使用some contortion之外,你不能真正看到grep查看跨越多行的表达式 - 并且POSIX没有指定-z选项。

你可以这样做:

#!/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从目录名打印&#34;直到下一个空行&#34;。那只是

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 表示&#34;如果该行为空,则退出,否则打印它&#34;。

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

{/^$/q;p;}

备注:非GNU seds(就像在macOS中找到的那样)可能会抱怨花括号中的两个命令;使用{{1}}代替(额外的分号)可能会有所帮助。

答案 2 :(得分:1)

它可以完全在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

答案 3 :(得分:1)

不要将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