【问题标题】:Extract Filename before date Bash shellscript在日期之前提取文件名 Bash shellscript
【发布时间】:2017-09-27 18:53:32
【问题描述】:

我正在尝试提取文件名的一部分 - 日期和后缀之前的所有内容。我不确定在 bashscript 中执行此操作的最佳方法。正则表达式?

名称是文件名的一部分。我正在尝试将其存储在 shellscript 变量中。前缀不会包含奇怪的字符。后缀将是相同的。这些文件存储在一个目录中 - 我将使用循环来提取每个文件的文件名部分。

预期的输入文件:

EXAMPLE_FILE_2017-09-12.out
EXAMPLE_FILE_2_2017-10-12.out

预期提取:

EXAMPLE_FILE
EXAMPLE_FILE_2

尝试:

  filename=$(basename "$file")
  folder=sed '^s/_[^_]*$//)' $filename
  echo 'Filename:' $filename
  echo 'Foldername:' $folder

【问题讨论】:

  • folder=sed '^s/_[^_]*$//)' $filename 行有三次问题。曾经是因为您省略了 shell 命令周围的$(…);一次是因为您希望sed 编辑文件名,而不是文件本身;一次是因为第一个 ^ 是不需要的。可行的是folder=$(sed 's/_[^_]*$//)' <<< "$filename") 或(不使用此处的字符串)folder=$(echo "$filename" | sed 's/_[^_]*$//)')。如果您精确复制,您可能会收到一条关于找不到命令 ^s/_[^_*$//) 的错误消息。

标签: regex linux bash shell


【解决方案1】:
$ cat file.txt
EXAMPLE_FILE_2017-09-12.out
EXAMPLE_FILE_2_2017-10-12.out
$


$ cat file.txt | sed 's/_[0-9]*-[0-9]*-[0-9]*\.out$//'
EXAMPLE_FILE
EXAMPLE_FILE_2
$

【讨论】:

  • 其实我没说。但堆栈溢出没有显示 *.在我放反斜杠之后,它显示 * 喜欢转义。后来堆栈溢出改变了我的编辑。令人困惑。
  • 啊,我明白了。我的错,因为我缩进了你的代码并没有发现。对于代码 sn-ps,尤其是涉及正则表达式等的内容,将文本缩进为“代码”,这意味着行首有四个空格(如果您希望材料缩进,则更多,例如在 @ 内的 C 语句块中987654322@。在运行文本中,键入*italics* 呈现为斜体**bold** 呈现为粗体,您可以使用下划线代替星号。您可以使用反引号附上代码材料——这就是我输入*italics*的方式。在评论中显示反引号是很痛苦的:this`has`backticks
  • 另外,要缩进一段代码,输入你想要的样子,忽略预览。然后选择代码并使用编辑框上方的 {} 按钮将其全部缩进。 (不要使用标签;不要复制标签;它们会让所有人感到困惑。)
  • 无用的猫。应该使用sed ... file.txt
【解决方案2】:

不需要无用的猫、昂贵的叉子和管道。外壳可以很好地切割字符串:

$ file=EXAMPLE_FILE_2_2017-10-12.out
$ echo ${file%%_????-??-??.out}
EXAMPLE_FILE_2

在友好的 shell 手册中阅读有关如何使用 %%、%、## 和 # 运算符的所有信息。

【讨论】:

  • 或者更简单的${file%_*}
【解决方案3】:

Bash 本身具有正则表达式功能,因此您无需运行实用程序。示例:

for fn in *.out; do
    [[ $fn =~ ^(.*)_[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2} ]]
    cap="${BASH_REMATCH[1]}"
    printf "%s => %s\n" "$fn" "$cap"
done

使用示例文件,输出为:

EXAMPLE_FILE_2017-09-12.out => EXAMPLE_FILE
EXAMPLE_FILE_2_2017-10-12.out => EXAMPLE_FILE_2

使用 Bash 本身将比为每个文件名生成 sedawk 等更快、更有效。

当然在使用中,你会想要测试一个成功的匹配:

for fn in *.out; do
    if [[ $fn =~ ^(.*)_[[:digit:]]{4}-[[:digit:]]{2}-[[:digit:]]{2} ]]; then
       cap="${BASH_REMATCH[1]}"
       printf "%s => %s\n" "$fn" "$cap"
    else
        echo "$fn no match"
    fi  
done

附带说明,如果您只需要修剪文件名中最后一个 _ 之后的字符串,则可以使用 Bash parameter expansion 而不是正则表达式:

for fn in *.out; do
    cap="${fn%_*}"
    printf "%s => %s\n" "$fn" "$cap"
done

然后针对$fn 测试$cap。如果它们相等,则参数扩展不会修剪_ 之后的文件名,因为它不存在。

正则表达式允许测试类似日期的字符串\d\d\d\d-\d\d-\d\d_ 之后。由你决定。

【讨论】:

    【解决方案4】:

    代码

    See this code in use here

    ^\w+(?=_)
    

    结果

    输入

    EXAMPLE_FILE_2017-09-12.out
    EXAMPLE_FILE_2_2017-10-12.out
    

    输出

    EXAMPLE_FILE
    EXAMPLE_FILE_2
    

    说明

    • ^ 在行首断言位置
    • \w+ 匹配任意单词字符 (a-zA-Z0-9_) 1 次到无限次
    • (?=_) 正向前瞻确保后面是下划线 _ 字符

    【讨论】:

      【解决方案5】:

      只需 sed

      sed 's/_[^_]*$//' file
      

      输出:

      EXAMPLE_FILE
      EXAMPLE_FILE_2
      

      ---------

      如果遍历扩展名为 .out 的文件列表 - bash 解决方案:

      for f in *.out; do echo "${f%_*}"; done
      

      【讨论】:

        【解决方案6】:
        awk -F_ 'NF-=1' OFS=_ file
        
        EXAMPLE_FILE
        EXAMPLE_FILE_2
        

        【讨论】:

          【解决方案7】:

          您能否也试试 awk 解决方案,它会处理所有的 .out 文件,请注意这是在 GNU awk 中编写和测试的。

          awk  --re-interval 'FNR==1{if(val){close(val)};split(FILENAME, array,"_[0-9]{4}-[0-9]{2}-[0-9]{2}");print array[1];val=FILENAME;nextfile}' *.out
          

          另外我的 awk 版本很旧,所以我使用 --re-interval,如果你有最新版本的 awk,那么你可能不需要使用它。

          解的解释和非单线形式:在这里也添加非单线形式的解。

          awk --re-interval '##Using --re-interval for supporting ERE in my OLD awk version, if OP has new version of awk it could be removed.
          FNR==1{            ##Checking here condition that when very first line of any Input_file is being read then do following actions.
            if(val){         ##Checking here if variable named val value is NOT NULL then do following.
                close(val)   ##close the Input_file named which is stored in variable val, so that we will NOT face problem of TOO MANY FILES OPENED, so it will be like one file read close it in background then.
          };
            split(FILENAME, array,"_[0-9]{4}-[0-9]{2}-[0-9]{2}");##Splitting FILENAME(which will have Input_file name in it) into array named array only, whose separator is a 4 digits-2 digits- then 2 digits, actually this will take care of YYYY-MM-DD format in Input_file(s) and it will be easier for us to get the file name part.
            print array[1];  ##Printing array 1st element here.
            val=FILENAME;    ##Storing FILENAME variable value which will have current Input_file name in it to variable named val, so that we could close it in background.
            nextfile         ##nextfile as it name suggests it will skip all the lines in current line and jump onto the next file to save some cpu cycles of our system.
          }
          ' *.out            ##Mentioning all *.out Input_file(s) here.
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2017-12-09
            • 2023-01-24
            • 2017-08-11
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-04-04
            • 1970-01-01
            相关资源
            最近更新 更多