迭代包含空格的文件列表

2024-09-30 14:00:00
admin
原创
88
摘要:问题描述:我想迭代文件列表。此列表是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 来做这种事简直是自毁前程。

相关推荐
  为什么项目管理通常仍然耗时且低效?您是否还在反复更新电子表格、淹没在便利贴中并参加每周更新会议?这确实是耗费时间和精力。借助软件工具的帮助,您可以一目了然地全面了解您的项目。如今,国内外有足够多优秀的项目管理软件可以帮助您掌控每个项目。什么是项目管理软件?项目管理软件是广泛行业用于项目规划、资源分配和调度的软件。它使项...
项目管理软件   601  
  华为IPD与传统研发模式的8大差异在快速变化的商业环境中,产品研发模式的选择直接决定了企业的市场响应速度和竞争力。华为作为全球领先的通信技术解决方案供应商,其成功在很大程度上得益于对产品研发模式的持续创新。华为引入并深度定制的集成产品开发(IPD)体系,相较于传统的研发模式,展现出了显著的差异和优势。本文将详细探讨华为...
IPD流程是谁发明的   7  
  如何通过IPD流程缩短产品上市时间?在快速变化的市场环境中,产品上市时间成为企业竞争力的关键因素之一。集成产品开发(IPD, Integrated Product Development)作为一种先进的产品研发管理方法,通过其结构化的流程设计和跨部门协作机制,显著缩短了产品上市时间,提高了市场响应速度。本文将深入探讨如...
华为IPD流程   9  
  在项目管理领域,IPD(Integrated Product Development,集成产品开发)流程图是连接创意、设计与市场成功的桥梁。它不仅是一个视觉工具,更是一种战略思维方式的体现,帮助团队高效协同,确保产品按时、按质、按量推向市场。尽管IPD流程图可能初看之下显得错综复杂,但只需掌握几个关键点,你便能轻松驾驭...
IPD开发流程管理   8  
  在项目管理领域,集成产品开发(IPD)流程被视为提升产品上市速度、增强团队协作与创新能力的重要工具。然而,尽管IPD流程拥有诸多优势,其实施过程中仍可能遭遇多种挑战,导致项目失败。本文旨在深入探讨八个常见的IPD流程失败原因,并提出相应的解决方法,以帮助项目管理者规避风险,确保项目成功。缺乏明确的项目目标与战略对齐IP...
IPD流程图   8  
热门文章
项目管理软件有哪些?
云禅道AD
禅道项目管理软件

云端的项目管理软件

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

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

内置subversion和git源码管理

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

免费试用