处理可能带有空格的文件名列表的 POSIX 兼容方式

Eer*_*nen 16 shell-script filenames posix quoting whitespace

我已经看到 Bash 脚本指南建议使用数组来处理包含空格的文件名。然而,DashAsBinSh表明数组不可移植,所以我正在寻找一种符合 POSIX 的方式来处理可能包含空格的文件名列表。

我正在寻找修改下面的示例脚本,以便它 echo

foo/target/a.jar
foo/target/b.jar
bar/target/lol whitespace.jar
Run Code Online (Sandbox Code Playgroud)

这是脚本

#!/usr/bin/env sh

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"
# this would be produced by a 'ls' command
# We can execute the ls within the script, if it helps

dostuffwith() { echo $1; };

F_LOCATIONS=$INPUT
ALL_FILES=$(for f in $F_LOCATIONS; do echo `basename $f`; done)
ALL_FILES=$(echo "$ALL_FILES" | sort | uniq)

for f in $ALL_FILES
do
    fpath=$(echo "$F_LOCATIONS" | grep -m1 $f)
    dostuffwith $fpath
done
Run Code Online (Sandbox Code Playgroud)

Gil*_*il' 12

POSIX壳具有一个阵列:所述位置参数($1$2等,统称refered为"$@")。

set -- 'foo/target/a.jar' 'foo/target/b.jar' 'bar/target/b.jar' 'bar/target/lol whitespace.jar'
set -- "$@" '/another/one at the end.jar'
…
for jar do
  dostuffwith "$jar"
done
Run Code Online (Sandbox Code Playgroud)

这很不方便,因为只有一个,而且它破坏了位置参数的任何其他用途。位置参数是函数的局部参数,这有时是一种祝福,有时也是一种诅咒。

如果您的文件名保证不包含换行符,您可以使用换行符作为分隔符。展开变量时,首先关闭通配符set -f并将字段拆分字符列表设置为IFS仅包含换行符。

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"
…
set -f; IFS='
'                           # turn off variable value expansion except for splitting at newlines
for jar in $INPUT; do
  set +f; unset IFS
  dostuffwith "$jar"        # restore globbing and field splitting at all whitespace
done
set +f; unset IFS           # do it again in case $INPUT was empty
Run Code Online (Sandbox Code Playgroud)

通过换行符分隔列表中的项目,您可以有效地使用许多文本处理命令,尤其是sort.

请记住始终在变量替换周围放置双引号,除非您明确希望发生字段拆分(以及通配,除非您已将其关闭)。


phe*_*mer 5

由于您的$INPUT变量使用换行符作为分隔符,我将假设您的文件名称中没有换行符。因此,是的,有一种简单的方法可以迭代文件并保留空白。

这个想法是使用read内置的shell。通常read会在任何空白处拆分,因此空格会破坏它。但是您可以设置IFS=$'\n',它只会在换行符上拆分。因此,您可以遍历列表中的每一行。

这是我能想到的最小的解决方案:

INPUT="foo/target/a.jar
foo/target/b.jar
bar/target/b.jar
bar/target/lol whitespace.jar"

dostuffwith() {
    echo "$1"
}

echo "$INPUT" | awk -F/ '{if (!seen[$NF]++) print }' | \
while IFS=$'\n' read file; do
  dostuffwith "$file"
done
Run Code Online (Sandbox Code Playgroud)

基本上它会awk根据文件名将“$INPUT”发送到其中进行重复数据删除(/如果之前没有看到最后一个项目,它会拆分然后打印该行)。然后一旦 awk 生成了文件路径列表,我们就使用它while read来遍历列表。