迭代包含空格的文件列表

2024-09-30 14:00:00
admin
原创
107
摘要:问题描述:我想迭代文件列表。此列表是find命令的结果,因此我想出了:getlist() { for f in $(find . -iname "foo*") do echo "File found: $f" # ...

问题描述:

我想迭代文件列表。此列表是find命令的结果,因此我想出了:

getlist() {
  for f in $(find . -iname "foo*")
  do
    echo "File found: $f"
    # do something useful
  done
}

除文件名称中包含空格外,其他都正常:

$ ls
foo_bar_baz.txt
foo bar baz.txt

$ getlist
File found: foo_bar_baz.txt
File found: foo
File found: bar
File found: baz.txt

我该怎么做才能避免空间分裂?


解决方案 1:

您可以用基于行的迭代替换基于单词的迭代:

find . -iname "foo*" | while read f
do
    # ... loop body
done

解决方案 2:

有几种可行的方法可以实现这一点。

如果您想要严格遵循原始版本,可以这样做:

getlist() {
        IFS=$'
'
        for file in $(find . -iname 'foo*') ; do
                printf 'File found: %s
' "$file"
        done
}

如果文件名中包含文字换行符,此操作仍会失败,但空格不会破坏它。

但是,没有必要与 IFS 打交道。以下是我首选的方法:

getlist() {
    while IFS= read -d $'' -r file ; do
            printf 'File found: %s
' "$file"
    done < <(find . -iname 'foo*' -print0)
}

如果您发现< <(command)语法不熟悉,则应阅读有关进程替换的内容。与此相比,此方法的优点for file in $(find ...)是可以正确处理带有空格、换行符和其他字符的文件。这是因为findwith-print0将使用null(aka ) 作为每个文件名的终止符,并且与换行符不同,null 不是文件名中的合法字符。

与几乎相同的版本相比,此版本的优势

getlist() {
        find . -iname 'foo*' -print0 | while read -d $'' -r file ; do
                printf 'File found: %s
' "$file"
        done
}

是否保留了 while 循环主体中的任何变量赋值。也就是说,如果您while像上面一样通过管道传输到,则主体while位于子 shell 中,这可能不是您想要的。

过程替换版本的优势find ... -print0 | xargs -0很小:xargs如果您需要的只是打印一行或对文件执行单个操作,则该版本就很好,但如果您需要执行多个步骤,则循环版本更容易。

编辑:这是一个很好的测试脚本,这样你就可以了解解决这个问题的不同尝试之间的区别

#!/usr/bin/env bash

dir=/tmp/getlist.test/
mkdir -p "$dir"
cd "$dir"

touch       'file not starting foo' foo foobar barfoo 'foo with spaces'\n    'foo with'$'
'newline 'foo with trailing whitespace      '

# while with process substitution, null terminated, empty IFS
getlist0() {
    while IFS= read -d $'' -r file ; do
            printf 'File found: '"'%s'"'
' "$file"
    done < <(find . -iname 'foo*' -print0)
}

# while with process substitution, null terminated, default IFS
getlist1() {
    while read -d $'' -r file ; do
            printf 'File found: '"'%s'"'
' "$file"
    done < <(find . -iname 'foo*' -print0)
}

# pipe to while, newline terminated
getlist2() {
    find . -iname 'foo*' | while read -r file ; do
            printf 'File found: '"'%s'"'
' "$file"
    done
}

# pipe to while, null terminated
getlist3() {
    find . -iname 'foo*' -print0 | while read -d $'' -r file ; do
            printf 'File found: '"'%s'"'
' "$file"
    done
}

# for loop over subshell results, newline terminated, default IFS
getlist4() {
    for file in "$(find . -iname 'foo*')" ; do
            printf 'File found: '"'%s'"'
' "$file"
    done
}

# for loop over subshell results, newline terminated, newline IFS
getlist5() {
    IFS=$'
'
    for file in $(find . -iname 'foo*') ; do
            printf 'File found: '"'%s'"'
' "$file"
    done
}


# see how they run
for n in {0..5} ; do
    printf '

getlist%d:
' $n
    eval getlist$n
done

rm -rf "$dir"

解决方案 3:

还有一个非常简单的解决方案:依赖 bash 通配符

$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid   file 3"
$ ls
stupid   file 3  stupid file1     stupid file2
$ for file in *; do echo "file: '${file}'"; done
file: 'stupid   file 3'
file: 'stupid file1'
file: 'stupid file2'

请注意,我不确定这种行为是否是默认行为,但我在我的商店中没有看到任何特殊设置,所以我会说它应该是“安全的”(在 osx 和 ubuntu 上测试)。

解决方案 4:

find . -iname "foo*" -print0 | xargs -L1 -0 echo "File found:"

解决方案 5:

find . -name "fo*" -print0 | xargs -0 ls -l

man xargs

解决方案 6:

由于您没有使用 进行任何其他类型的过滤find,因此您可以使用以下bash4.0 版本中的功能:

shopt -s globstar
getlist() {
    for f in **/foo*
    do
        echo "File found: $f"
        # do something useful
    done
}

**/匹配零个或多个目录,因此完整模式将匹配foo*当前目录或任何子目录。

解决方案 7:

我真的很喜欢循环和数组迭代,所以我想我会将这个答案添加到组合中......

我也喜欢 marchelbling 的愚蠢文件示例。:)

$ mkdir test
$ cd test
$ touch "stupid file1"
$ touch "stupid file2"
$ touch "stupid   file 3"

在测试目录中:

readarray -t arr <<< "`ls -A1`"

这会将每个文件列表行添加到一个 bash 数组中,arr并删除所有尾随的换行符。

假设我们想给这些文件起更好的名字……

for i in ${!arr[@]}
do 
    newname=`echo "${arr[$i]}" | sed 's/stupid/smarter/; s/  */_/g'`; 
    mv "${arr[$i]}" "$newname"
done

${!arr[@]} 扩展为 0 1 2,因此“${arr[$i]}”是数组的第 i 个元素。变量两边的引号对于保留空格很重要。

结果是三个重命名的文件:

$ ls -1
smarter_file1
smarter_file2
smarter_file_3

解决方案 8:

find有一个-exec参数,用于循环查找结果并执行任意命令。例如:

find . -iname "foo*" -exec echo "File found: {}" ;

这里{}代表找到的文件,将其包装起来""可以让生成的 shell 命令处理文件名中的空格。

在很多情况下,您可以用 替换最后一个;(启动一个新命令)+,这会将多个文件放入一个命令中(但不一定一次放入所有文件,man find有关更多详细信息,请参阅)。

解决方案 9:

我最近不得不处理类似的情况,并且我建立了一个FILES数组来迭代文件名:

eval FILES=($(find . -iname "foo*" -printf '"%p" '))

这里的想法是将每个文件名用双引号括起来,用空格分隔它们,并使用结果初始化数组FILES。必须使用eval才能正确评估输出中的双引号find以进行数组初始化。

要迭代文件,只需执行以下操作:

for f in "${FILES[@]}"; do
    # Do something with $f
done

解决方案 10:

在某些情况下,如果您只需要复制或移动文件列表,也可以将该列表通过管道传输到 awk。字段周围

很重要(简而言之,您的文件,一行列表 = 一个文件)。"" ""`$0`

find . -iname "foo*" | awk '{print "mv \""$0"\" ./MyDir2" | "sh" }'

解决方案 11:

好的-这是我在 Stack Overflow 上的第一篇帖子!

虽然我遇到的问题一直出现在 csh 而不是 bash 中,但我确信我提供的解决方案在这两种情况下都适用。问题在于 shell 对“ls”返回的解释。我们可以通过使用通配符的 shell 扩展来从问题中移除“ls” *- 但如果当前(或指定文件夹)中没有文件,这将给出“不匹配”错误 - 为了解决这个问题,我们只需扩展扩展以包含点文件即可:* .*- 这将始终产生结果,因为文件 . 和 .. 将始终存在。因此在 csh 中,我们可以使用这个构造...

foreach file (* .*)
   echo $file
end

如果你想过滤掉标准点文件那么这很容易......

foreach file (* .*)
   if ("$file" == .) continue
   if ("file" == ..) continue
   echo $file
end

该线程中第一篇文章中的代码应这样写:-

getlist() {
  for f in $(* .*)
  do
    echo "File found: $f"
    # do something useful
  done
}

希望这有帮助!

解决方案 12:

另一个工作解决方案......

目标是:

  • 在目录中递归选择/过滤文件名

  • 处理每个名称(无论路径中的空格......)

#!/bin/bash  -e
## @Trick in order handle File with space in their path...
OLD_IFS=${IFS}
IFS=$'
'
files=($(find ${INPUT_DIR} -type f -name "*.md"))
for filename in ${files[*]}
do
      # do your stuff
      #  ....
done
IFS=${OLD_IFS}


解决方案 13:

Powershell 适用于 Linux。只需安装 Powershell 并执行

get-childItems|foreach{ #or for the terse version "gci|%{"
  # in the forEach loop $_ is the iterator,
  # in this case the file object
  do-stuffWithFIleName $_.name
  # you want to change myfile.baz into myfile.foo? no wukkas:
  doSomethingWithADifferentExtension $_.name.replace($_.extension, ".foo")
  # $_.name is the name of the file with extension, or…
  do-otherstuffWithFullPath $_.fullname
  # $_.fullname is the absolute path, or…
  do-evenMoreStuffWith $_.lastWriteTime
  # because PWSH is object-oriented, you can use a bunch of the item's properties
  # ..profit
}

说真的,使用 posix shell 来做这种事简直是自毁前程。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   681  
  在项目管理领域,集成产品开发(IPD)流程以其高效、协同的特点,被众多企业视为提升产品竞争力的关键。IPD流程强调跨部门、跨职能的紧密合作,以确保产品从概念到市场各个环节的无缝衔接。然而,实现这一目标并非易事,它需要企业深刻理解并掌握IPD流程中的跨部门协作艺术。本文将深入探讨IPD流程中跨部门协作的三个关键点,旨在为...
IPD项目管理咨询   9  
  掌握IPD流程图:提升团队协作的关键路径在当今快速变化的商业环境中,团队协作的效率与效果直接关系到项目的成功与否。集成产品开发(Integrated Product Development,简称IPD)作为一种先进的研发管理理念,通过跨部门、跨领域的协同工作,能够显著提升产品开发的速度与质量。而IPD流程图,则是这一理...
IPD流程阶段   9  
  IPD流程概述:理解其核心价值与实施背景集成产品开发(Integrated Product Development,简称IPD)是一种先进的产品开发管理理念,它强调跨部门协作、市场导向和快速响应变化的能力。IPD流程不仅关注产品本身的技术创新,更注重将市场、研发、生产、销售等各个环节紧密集成,以实现产品从概念到市场的高...
华为IPD是什么   7  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程以其跨部门协作、高效决策和快速响应市场变化的特点,被众多企业视为提升竞争力的关键。然而,实践IPD流程并非易事,项目管理中的种种错误往往阻碍了其效果的充分发挥。本文旨在深入探讨如何在实施IPD流程时避免这些常见错误,...
IPD框架   7  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

尊享禅道项目软件收费版功能

无需维护,随时随地协同办公

内置subversion和git源码管理

每天备份,随时转为私有部署

免费试用