【问题标题】:Recursively list CSV of filename,dir递归列出文件名、目录的 CSV
【发布时间】:2017-11-22 05:07:07
【问题描述】:

假装我的根是: foo

我有一个文件夹结构:

-foo
  -123
    -file.jpg
    -file2.jpg
  -456
    -file.jpg
    -file2.jpg

该目录可能更深一些文件夹。

如果我有这样的根:

ROOT=foo

如何在 shell 脚本中以 CSV 格式列出每个文件名及其路径?

我尝试了一些答案,但我很难让它输出一长串 CSV。

我希望是这样的:

file.jpg,123,file2.jpg,123,file.jpg,456,file2.jpg,456

所以,我尝试了这个:

# make a var for files in current folder
arr=(./*)
CSVRETURN=""
# simple loop to iterate files
for ((i=0; i<${#arr[@]}; i++)); do
    #do something to each element of array
    CSVRETURN="$CSVRETURN","${arr[$i]}"
done
echo $CSVRETURN

它不断在我的输出中添加句点,我不相信我什至没有正确的递归部分。有人可以指出我正确的方向或可能有帮助的文档吗?

我对 shell 脚本做的很少,但我正在尝试 :) 基本上会采用这个答案并将整个目录导出到 CSV,并提供带有其目录路径(相对于初始目录)的图像链接轻松过滤以导入 Excel。

【问题讨论】:

  • 这不是一个理智的输出格式。
  • 您想以真正 CSV 可解析的形式输出吗?在这种情况下,您需要考虑到文件名也可以包含逗号或换行符。我不会从命令行手动创建 CSV 格式,而是使用可用于各种编程语言的众多 CSV 库之一。

标签: bash shell loops csv recursion


【解决方案1】:

如果您有GNU find,则可以轻松完成此操作,而不是bash

find foo -type f -printf '"%f","%h"\n' | paste -d, -s -
  • %f 用于文件名
  • %h 用于前导目录路径
  • paste -d, -s - 将换行符分隔的结果连接成逗号分隔的单行

或者使用BSD find 的更通用的解决方案,因为它不支持-printf 选项

find foo -type f -print | sed 's|^\(.*\)/\(.*\)|"\2","\1"|' | paste -d, -s -

输出

"file1.jpg","foo/123","file2.jpg","foo/123","file1.jpg","foo/456","file2.jpg","foo/456"

要生成快速测试用例,您可以使用

mkdir -p foo/{123,456}
touch foo/{123,456}/file{1..2}.jpg

生成类似的目录结构

foo/
├── 123
│   ├── file1.jpg
│   └── file2.jpg
└── 456
    ├── file1.jpg
    └── file2.jpg

【讨论】:

  • 这不是一个真正的逗号分隔列表。如果文件名本身有逗号怎么办?
  • @hmedia1 很好,在这种情况下,可能在文件名和路径周围添加双引号会更安全,例如-printf "\"%f\",\"%h\"\n"
  • 如果它们包含双引号怎么办?
  • @perreal 我同意如果文件名设法包含这些特殊字符(逗号、双引号、换行符),使用专用的 csv 解析器可能会更安全、更干净。如果文件名包含双引号",我会尝试在管道中添加sed 's/"/""/' 以使嵌入的双引号有效
【解决方案2】:

如你所愿:

  • 无领先期
  • 处理文件夹
  • 单行 CSV

您可以有两种约定(根据我对您的示例的理解):

  1. 列表喜欢:
    <strong>full/folder</strong>/file  ,  <strong>full/folder</strong>/file
  2. 列表喜欢:
    <strong>full/folder</strong>  ,  file  ,  <strong>full/folder</strong>  ,  file

假设我当前的目录 foo 看起来像这样:

14:37:14 ツ :foo >ls -R
sublevel1        456            123

./sublevel1:
123

./sublevel1/123:
file2.jpg file.jpg

./456:
file2.jpg file.jpg

./123:
file2.jpg file.jpg

那么对于first约定:

#!/usr/bin/env bash
first=1
find . -type f -print0 | while IFS= read -rd '' file ; do 
    if [ "$first" == "1" ]; then 
        filestr="\"$(cut -f2- -d'/' <<< "${file}")\""
        unset first
   else 
        filestr=",\"$(cut -f2- -d'/' <<< "${file}")\""  
        fi
   printf "%s" "$filestr"
done

这会给你以下结果:

"sublevel1/123/file.jpg","sublevel1/123/file2.jpg","456/file.jpg","456/file2.jpg","123/file.jpg","123/file2.jpg"

如果您将 -print0 更改为 -printf "%h\0%f\0",那么您将获得类似于上述第二个约定的输出:

"sublevel1/123","file.jpg","sublevel1/123","file2.jpg","456","file.jpg","456","file2.jpg","123","file.jpg","123","file2.jpg"

注意事项:

  • null (\0 , -print0) 处理方式使其在处理奇怪的文件名时更加可靠,甚至可能是嵌入换行符的文件名。
  • printf "%s" 格式在将字符串正确传递给程序时也能很好地处理名称
  • if 块只是为了确保第一个文件没有前缀逗号
  • “while”循环的低效率对于您想要这样的单个字符串 csv 列表的任何可能用途来说都不会引起注意。
  • 我添加了 sublevel1 来演示多个文件夹级别。

对于更健壮的应用程序,请考虑序列化 JSON,或其他可以更完整地处理列表的数据表示格式。

【讨论】:

    猜你喜欢
    • 2016-07-11
    • 2013-03-04
    • 2011-07-11
    • 2010-10-19
    • 2013-10-26
    • 2021-01-05
    • 2014-07-12
    • 2010-10-04
    • 1970-01-01
    相关资源
    最近更新 更多