【问题标题】:Best way to group filename based on filename in Bash?根据 Bash 中的文件名对文件名进行分组的最佳方法?
【发布时间】:2020-09-18 10:52:16
【问题描述】:

我有一个包含以下文件的文件夹:

DA-001-car.jpg
DA-001-dog.jpg
DA-001-coffee.jpg
DA-002-house.jpg
DA-003-coffee.jpg
DA-003-cat.jpg
...

我想生成这个 (CSV) 输出:

SKU, IMAGE
DA-001, "DA-001-car.jpg, DA-001-dog.jpg, DA-001-coffee.jpg"
DA-002, "DA-001-house.jpg"
DA-003, "DA-001-coffee.jpg, DA-001-cat.jpg"

我尝试在 Bash 中对此进行编程:

#!/bin/bash
echo "SKU, FILE" >> tmp.csv
for file in /home/calvin/test/*.jpg
do
    SKU_NAME="${file##*/}"
    echo ${SKU_NAME:0:6}, \"inner for-loop?, ?, ?\" >> tmp.csv
done
uniq tmp.csv output.csv

如您所见,我是编程菜鸟 :) 请帮帮我,提前谢谢!

【问题讨论】:

    标签: bash csv for-loop awk uniq


    【解决方案1】:

    这样就可以了。这需要 GNU awk 以升序输出。如果您不关心订单,可以使用任何旧的awk 并删除PROCINFO

    #!/bin/bash
    
    awk -F- '
      BEGIN{
        print "SKU, IMAGE"
      }
      {
        sep=!a[$2]?"":", "
        a[$2]=a[$2] sep $0
      }
      END{
        PROCINFO["sorted_in"] = "@ind_str_asc" # GNU only feature
        for(i in a){print "DA-" i ", " "\"" a[i] "\""}
      }' <(find /home/calvin/test -type f -name "*.jpg" -printf "%f\n") > ./tmp.csv
    

    示例输出

    $ cat ./tmp.csv
    SKU, IMAGE
    DA-001, "DA-001-coffee.jpg, DA-001-car.jpg, DA-001-dog.jpg"
    DA-002, "DA-002-house.jpg"
    DA-003, "DA-003-coffee.jpg, DA-003-cat.jpg"
    

    【讨论】:

    • 输出只有:SKU、IMAGE
    • find /home/calvin/test -type f -name "*.jpg" -printf "%f\n" 是否输出 jpg 文件列表?
    【解决方案2】:

    如果文件名不包含空格,您可以使用 sed 代替内部循环:

    printf '%s\n' *.jpg \
    | cut -f1,2 -d- \
    | sort -u \
    | while IFS= read -r sku ; do
        echo "$sku",\"$(echo "$sku"* | sed 's/ /, /')\"
    done
    

    使用内部循环,您可以从echo 切换到printf。 sed 用于删除结尾的逗号。

    printf '%s\n' *.jpg \
    | cut -f1,2 -d- \
    | sort -u \
    | while IFS= read -r sku ; do
        printf %s "$sku, \""
        for f in "$sku"* ; do
            printf '%s, ' "$f"
        done | sed 's/, $//'
        printf '"\n'
    done
    

    如果您不想解析ls 的输出并运行sort,您可以将前缀存储在关联数组中:

    #!/bin/bash
    declare -A prefix
    for jpg in *.jpg ; do
        p1=${jpg%%-*}
        jpg=${jpg#*-}
        p2=${jpg%%-*}
        prefix[$p1-$p2]=1
    done
    
    for sku in "${!prefix[@]}" ; do
        printf '%s, "' "$sku"
        for f in "$sku"* ; do
            printf '%s, ' "$f"
        done | sed 's/, $//'
        printf '"\n'
    done
    

    【讨论】:

    • 当我运行你的第一个代码时,DA-001-coffee.jpg 和 DA-001-dog.jpg 之间没有逗号
    【解决方案3】:
    awk '
        BEGIN {
            OFS = ", "
            print "SKU", "IMAGE"
            for (i=1; i<ARGC; i++) {
                curr = fname = ARGV[i]
                sub(/-[^-]+$/,"",curr)
                if ( curr != prev ) {
                    if ( i > 1 ) {
                        print prev, "\"" fnames "\""
                    }
                    prev = curr
                    fnames = ""
                }
                fnames = (fnames == "" ? "" : fnames OFS) fname
            }
            print prev, "\"" fnames "\""
            exit
        }
    ' /home/calvin/test/*.jpg
    SKU, IMAGE
    DA-001, "DA-001-car.jpg, DA-001-coffee.jpg, DA-001-dog.jpg"
    DA-002, "DA-002-house.jpg"
    DA-003, "DA-003-cat.jpg, DA-003-coffee.jpg"
    

    【讨论】:

    • 有没有不使用awk的方法。但只是使用带有 for-loop 或 while 的 Bash?这样我就可以轻松 ./run.sh
    • 在 awk 中进行任何文本操作都比在 shell 脚本中更简单、更健壮、执行更快、更便携。想要以./run.sh 运行它并不意味着您不能使用awk。无需将 awk 脚本保存在文件中并以awk -f that_script_file *.jpg 运行,只需将脚本放在单引号内并以awk 'that_script' *.jpg 运行即可。我更新了我的答案来做到这一点。
    【解决方案4】:

    作为所有回复和建议的结果,我正在使用此代码来实现所需的输出:

    #!/bin/bash
    
    echo "SKU, IMAGES" >> output.csv
    
    ls *.jpg | cut -f1,2 -d- | sort -u | while read SKU
    do
        echo $SKU, \"$(echo "$SKU"* | sed 's/ /, /g')\" >> output.csv
    done
    

    谢谢大家!

    【讨论】:

    猜你喜欢
    • 2016-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-02-27
    • 2017-11-04
    • 2012-07-07
    • 2015-01-20
    相关资源
    最近更新 更多