符合POSIX的方式使用可能包含空格的文件名列表


14

我看过Bash脚本指南,建议使用数组处理包含空格的文件名。但是DashAsBinSh建议数组不能移植,因此我正在寻找一种POSIX兼容方式来处理可能包含空格的文件名列表。

我正在尝试修改以下示例脚本,以便 echo

foo/target/a.jar
foo/target/b.jar
bar/target/lol whitespace.jar

这是脚本

#!/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

Answers:


8

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

这很不方便,因为只有一个,并且破坏了位置参数的任何其他使用。位置参数对于功能而言是局部的,这有时是一种祝福,有时是一种诅咒。

如果保证您的文件名不包含换行符,则可以使用换行符作为分隔符。当您展开变量时,首先关闭glob 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

列表中的项目之间用换行符分隔,因此可以特别有用地使用许多文本处理命令sort

请记住,除非您明确希望发生字段拆分(以及遍历,除非您已将其关闭),否则请始终在变量替换两边加上双引号。


好的答案和解释。我将其标记为已接受,因为这会使原始sort | uniq步骤按预期工作。
Eero Aaltonen

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

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


$ checkbashisms bar.sh bar.sh第14行中可能的bashism(<<<这里是字符串)
Eero Aaltonen

1
@EeroAaltonen更改为不使用herestring。请注意,尽管进行了此更改,但while循环因此dostuffwith在子shell中执行。因此,在循环完成时,对正在运行的shell进行的任何变量或更改都将丢失。唯一的选择是使用完整的Heredoc,这并不令人不愉快,但我认为这样做会更好。
Patrick

我奖励积分的依据更多是可读性,而不是小巧。这当然有效,并且已经为此+1。
Eero Aaltonen

IFS="\n"以反斜杠和n个字符分割。但在中read file,没有分裂。IFS="\n"仍然很有用,因为它从$ IFS删除了空白字符,否则这些空白字符将在输入的开头和结尾被剥离。读取一行,规范的语法IFS= read -r line,但IFS=anything read -r line(前提是任何不包含空格),也能发挥作用。
斯特凡Chazelas

哎呀 不知道我是如何做到的。固定。
Patrick
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.