【问题标题】:bash: filter list of files, that can contain spaces" - associative array required?bash:过滤文件列表,可以包含空格” - 需要关联数组吗?
【发布时间】:2020-01-05 08:33:22
【问题描述】:

我想创建一个文件名列表 - 其中可能包含一些带有空格的文件名。
此列表应在 bash 中过滤(而不是使用 'find' 本身 - 或类似的)。
必须以某种方式处理最终列表。
我无法让它工作 - 除非使用 associative 数组。

这是我的解决方案。

测试目录:

> find $HOME/test-dir/
/home/frank/test-dir/
/home/frank/test-dir/FileA
/home/frank/test-dir/File D
/home/frank/test-dir/FileC
/home/frank/test-dir/FileB

脚本 #1(有效):

> cat test2.sh 
#!/bin/bash

mapfile -t Data < <(find $HOME/test-dir/ -type f)

for Key in ${!Data[@]}
do
    echo "$Key -> ${Data[$Key]}"
done
echo

# remove #1 element via variable
Del=2
unset 'Data[$Del]'

while read Value
do
    echo "$Value"
done < <(IFS=$'\n'; for Value in ${Data[@]}; do echo $Value; done)
echo

注意:脚本末尾的进程替换应该能够处理仅循环内的值,而无需知道它存储在关联数组中(旧代码)。

输出:

> ./test2.sh 
0 -> /home/frank/test-dir/FileA
1 -> /home/frank/test-dir/File D
2 -> /home/frank/test-dir/FileC
3 -> /home/frank/test-dir/FileB

/home/frank/test-dir/FileA
/home/frank/test-dir/File D
/home/frank/test-dir/FileB

对于“文件 D”,任何使用纯数组的尝试都会失败。 我可以填充数组,但遍历或尝试删除元素会再次破坏它:

脚本 #2(不起作用):

> cat test2.sh 
#!/bin/bash

OLDIFS="$IFS"
IFS=$'\n'
readarray -t Data < <(find $HOME/test-dir/ -type f)
IFS="$OLDIFS"  # works only if i drop this

for Value in ${Data[@]}
do
    echo "$Value"
done
echo

# remove #1 element via variable
Del=2
unset 'Data[$Del]'

for Value in ${Data[@]}
do
    echo "$Value"
done

输出:

> ./test2.sh 
/home/frank/test-dir/FileA
/home/frank/test-dir/File
D
/home/frank/test-dir/FileC
/home/frank/test-dir/FileB

/home/frank/test-dir/FileA
/home/frank/test-dir/File
D
/home/frank/test-dir/FileB

有趣的是,删除 IFS 的恢复(参见上面的注释行)会导致

输出:

> ./test2.sh 
/home/frank/test-dir/FileA
/home/frank/test-dir/File D
/home/frank/test-dir/FileC
/home/frank/test-dir/FileB

/home/frank/test-dir/FileA
/home/frank/test-dir/File D
/home/frank/test-dir/FileB

但我想本地化 IFS 的设置以不干扰旧代码,这依赖于不同的 IFS 值。

有什么方法可以让它与纯数组(不是关联数组)一起工作?

附录

这也有效:

> cat test2b.sh 
#!/bin/bash

readarray -t Data < <(find $HOME/test-dir/ -type f)

while read Value
do
    echo "$Value"
done < <(IFS=$'\n'; for Value in ${Data[@]}; do echo $Value; done)
echo

# remove #1 element via variable
Del=2
unset 'Data[$Del]'

while read Value 
do
    echo "$Value"
done < <(IFS=$'\n'; for Value in ${Data[@]}; do echo $Value; done)
echo

但是不得不这样走有点奇怪。我使用关联数组来替代我的解决方案。但那是因为我自己介绍了 key->value 并且必须只返回值。对于纯数组要求这样做感觉很奇怪。

【问题讨论】:

标签: arrays bash


【解决方案1】:

我给你的建议如下:

  • 使用find 列出文件并在while 循环中处理它们
  • find 打印文件名,后跟一个空字符而不是换行符
  • 在循环中进行选择。

看起来像这样:

#!/usr/bin/env bash
del=2
counter=0
find $HOME/test-dir/ -type f -print0 | while read -d $'\0' file; do
   # ignore element
   (( counter++ == del )) && continue
   # perform action
   echo "$file"
done

【讨论】:

    【解决方案2】:

    考虑使用 'printf' 将数组转换为换行符分隔值。更紧凑,不会被以“-”开头的文件名欺骗(看起来像选项)。

    while read Value 
    do
        echo "$Value"
    done < <(printf '%s\n' "${Data[@]}" )
    

    【讨论】:

    • @kvantour 是的,但在这种情况下,OP 专门要求一个脚本来处理带有非特殊字符 + 空格的文件名。无需处理新线路。如果存在这样的要求,则需要更改脚本以处理以空字符结尾的文件名。
    【解决方案3】:

    问题的原因是你没有用双包裹${Data[@]} for 循环中的引号。然后数组${Data[@]} 扩展为 违背您意愿的 IFS 拆分单词列表。
    此外,您无需临时将 IFS 分配给 readarray 的换行符。

    然后script2 将如下所示:

    readarray -t Data < <(find $HOME/test-dir/ -type f)
    
    for Value in "${Data[@]}"
    do
        echo "$Value"
    done
    echo
    
    # remove an element via variable
    Del=2
    unset 'Data[$Del]'
    
    for Value in "${Data[@]}"
    do
        echo "$Value"
    done
    

    顺便说一句,正如@kvantour 建议的那样,强烈建议使用 NUL 字符 作为文件名分隔符而不是换行符。然后readline这句话 看起来像:

    while IFS= read -r -d "" f; do
        Data+=("$f")
    done < <(find $HOME/test-dir/ -type f -print0)
    

    如果readarray 支持-d 选项(bash >= 4.4),您也可以说:

    readarray -t -d "" Data < <(find $HOME/test-dir/ -type f -print0)
    

    希望这会有所帮助。

    【讨论】:

      猜你喜欢
      • 2021-06-16
      • 1970-01-01
      • 2015-10-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-26
      相关资源
      最近更新 更多