【问题标题】:How to extract file name from piped results?如何从管道结果中提取文件名?
【发布时间】:2016-05-19 23:17:23
【问题描述】:

我有一个包含以下格式文件的文本文件:

item1 a/b/c/d/file1.csv
item2 a/b/c/d/file2.csv
item3 a/b/c/d/file3.csv
and so on...

为了隔离每行中的第二个项目,我使用以下内容:

cat mn_s3_files.txt | awk '{ print $1 }'

产量:

a/b/c/d/file1.csv
a/b/c/d/file2.csv
a/b/c/d/file3.csv
and so on...

现在,我怎样才能从管道结果中只提取基本名称?

例如:

cat mn_s3_files.txt | awk '{ print $1 }' | <some basename command here>

想要的输出:

file1
file2
file3
and so on...

【问题讨论】:

    标签: linux bash pipe filenames


    【解决方案1】:

    使用 basename :) 以下方法在您的输入中对我有用,但可能会遇到带引号的字符串等问题(感谢 Charles 指出这一点)。

    cat mn_s3_files.txt  | awk '{print $2}' | xargs basename
    

    在 Linux 上,您可以使用 -d 和 xargs 来逐字处理所有字符。如果您收到 extra operand 错误,请尝试以下操作:

    cat foo | awk '{print $2}' | perl -ne '$_ =~ s[.*/(.*)][$1]; print "$_";'
    

    perl 版本非常暴力,即剥离所有内容,直到最后一行/。在这种情况下,您也许可以删除 awk。

    如果你想在一行中完成

    perl -ne 's[.*/(.*)][$1]; print "$_"'  mn_s3_files.txt
    

    或者您可以使用自动拆分并将分隔符更改为/

    perl -F'/' -ane 'print "$F[4]"'  mn_s3_files.txt
    

    读者须知。

    这个答案的其余部分试图回答来自 cmets 中 Charles 的问题。请注意,他关于 xargs 和引号的观点是完全有效的,即它们可能会导致问题,但在这种情况下它们并没有给我带来任何问题。

    教育学

    对于使用命令行、纯 Bash 解决方案或使用多个可能的命令和管道的解决方案的人来说,什么更具教学意义?我认为这是一个主观问题,没有正确答案。我选择了一种解决方案,即与我在问题中可以看到的内容密切相关的一种解决方案,即 OP 理解管道和cat,所以让我们使用它并以此为基础。我选择不采用最佳解决方案,因为最佳可能意味着任何事情。我可以用 C/C++ 编写一个速度极快的版本,但这似乎有点矫枉过正,可能对 OP 没有帮助。

    Charles 对这个答案的一些 cmets 让我质疑自己对 *nix 的理解,所以我需要进一步解决这些问题。

    Charles 在他的回答中说了以下让我有些惊讶的内容,强调我的...

    您可以只使用 bash 中内置的功能来完成这一切 - 像 awk 或 xargs 或 basename 这样的任何东西都是不必要的低效率

    我决定对此进行测试,因为我没有在我使用过的机器上体验过这种情况,主要是多核 Mac 和 Linux。我在这里假设效率意味着运行脚本需要多长时间,因为如果应用于编写命令行等多长时间,这完全取决于使用它的人并且完全主观。我对纯 bash 解决方案进行了基准测试,即

    #!/bin/bash
    while read -r item path; do
      name=${path##*/}
      printf '%s\n' "$name"
    done <mn_s3_files.txt
    

    所用时间> 17分钟

    real    17m34.959s
    user    15m46.912s
    sys     1m44.981s
    

    这实际上比我想象的要长得多,事实上,在我创建的文件上,我最终杀死了两次脚本,以为出了点问题,因为我没想到它会这么慢。我仍然不相信有什么不对劲。 CPU 一直固定在 > 99%。

    Charles还提到了以下...

    awk 直接从 mn_s3_files.txt 读取比从 /bin/cat 写入的 FIFO 读取要快得多。

    我怀疑在单核机器上这可能是真的,但在多核机器上它不是much faster。请注意,cat 非常高效,并且实际上会将大部分时间花在 IO 上,因为在这种情况下。管道读取端的应用程序读取速度明显慢于cat 写入速度。我用一堆类似于 OP 的数据创建了一个大文件。

    time cat mn_s3_files.txt  | awk '{print $2}' > /dev/null 
    
    real    0m59.017s
    user    0m57.676s
    sys     0m1.833s
    

    相比

    time awk '{print $2}' < mn_s3_files.txt > /dev/null
    
    real    0m59.926s
    user    0m58.266s
    sys     0m1.468s
    

    在这种情况下,首先想到的可能是fastest,对很多人来说就是猫。运行以下命令时

    time cat mn_s3_files.txt  | awk '{print $2}' | perl -ne '$_ =~ s[.*/(.*)][$1]; print "$_";' > /dev/null
    
    real    1m6.614s
    user    2m2.644s
    sys 0m4.221s
    

    cat 在我的机器上从未达到超过1% CPU。值得注意的是,awkPerl 在整个过程中的 CPU 使用率都接近 100%,即它的效率要低得多。

    Charles 提到start time 是他在讨论 bash 脚本时所指的效率提升...

    Re:效率——本机 while 读取循环的好处是启动时间,而不是长流的运行时性能。如果处理少量数据,您可能希望使用 bash-native 内置工具,以及处理大量数据的外部工具(例如 awk)(启动外部工具的时间被实际执行处理所花费的时间所淹没)。

    这对我来说似乎也违反直觉,所以我在小文件上对 bash 和 awk 进行了基准测试。对于一个只有三行的文件,启动时间对时间没有明显影响,在我的机器上多次运行时,awk 实际上快 整毫秒...

    time splitter.sh > /dev/null
    
    real    0m0.013s
    user    0m0.002s
    sys     0m0.006s
    

    是时候 awk...

    time awk '{gsub(/.*\//, "", $2); print $2}' < mn_s3_files2.txt > /dev/null
    
    real    0m0.013s
    user    0m0.002s
    sys     0m0.006s
    

    我也在一个空文件上做了这件事,而且 awk 更快。注意,此时我意识到查尔斯正在谈论在命令行中输入它,所以我尝试了,即

    time while read -r item path; do name=${path##*/}; printf '%s\n' "$name"; done <mn_s3_files2.txt;
    

    这是一个lot faster 而不是 awk (节省了高达 11 毫秒) 对于非常小的文件,即 完全扼杀性能 :)。

    地球上最快的打字员

    假设您是fastest typists on the planet 中的一员

    世界上最快的打字员的最佳打字速度是每个字母大约 50 毫秒(请注意,我忽略了您可能需要在两个版本中使用大量奇怪字符的事实)。 bash 版本中的字符数约为 90,这意味着如果您以每个字符 50 毫秒的惊人速度输入,则需要大约 4 秒。 awk 版本大约有 50 个字符,因此这将花费您大约 2.5 秒的时间来输入。

    因此,即使您是世界上最快的打字员,awk 版本也比 bash 版本快。

    Charles 在另一条评论中说...

    我不确定 cat mn_s3_files.txt | awk '{打印 $2}' | xargs basename 永远正确

    永远正确部分不正确。我对 xargs 的原始答案和给定的输入字符串适用于以下版本的 mac 10.11.5,使用来自 OP 的输入没有问题。

    【讨论】:

    • 奇怪,我收到以下错误:basename: extra operand
    • 让我测试一个不同的方法。给我 1 分钟。
    • 至少消除不必要的cat 调用。 awk 直接从 mn_s3_files.txt 读取比从 /bin/cat 写入的 FIFO 读取要快得多。为什么同时拥有awkperlawkbasenameawk 可以做一个或两个工作;它本身就是一种完整的编程语言,并具有完整的正则表达式支持。
    • 在一个命令中完成所有这些操作:awk '{gsub(/.*\//, "", $2); print $2}' &lt;mn_s3_files.txt
    • (另外,使用 xargs 而不使用 -d-0 扩展名是有风险的:默认行为将引号和空格视为语法,因此,如果您的文件名中包含文字引号 - - 特别是 odd 数量的文字引号,这样它开始引用但不关闭它 - 你会得到非常不愉快的行为)。
    【解决方案2】:
    awk -F'[/.]' '{print $5}' file
    file1
    file2
    file3
    

    【讨论】:

    • 只有在您确切知道有多少 /s 时才有效。现在,也许如果你使用$NR...
    • 这个纯代码的答案会更好一些文字解释它是如何工作的以及任何限制或假设(正如@CharlesDuffy 指出的那样)。
    【解决方案3】:

    您可以只使用 bash 内置的功能来完成这一切——像 awkxargsbasename 这样的任何东西都是不必要的低效率。

    while read -r item path; do
      name=${path##*/}
      printf 'Read %q from %q\n' "$item" "$name"
    done <mn_s3_files.txt
    

    ...屈服:

    read item1 from file1.csv
    read item2 from file2.csv
    

    显然,要仅发射 item1item2,请将其设为 printf '%s\n' "$name"

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-08-19
      • 1970-01-01
      • 1970-01-01
      • 2018-03-24
      • 2020-01-27
      • 2021-03-29
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多