命令替换:在换行符上分割,但不分割空格


30

我知道我可以通过几种方法解决此问题,但是我想知道是否有一种方法可以仅使用bash内置函数来实现,否则,最有效的方法是什么。

我有一个内容如下的文件

AAA
B C DDD
FOO BAR

我的意思是说它只有几行,每行可能有也可能没有空格。我想运行类似的命令

cmd AAA "B C DDD" "FOO BAR"

如果我使用cmd $(< file)我得到

cmd AAA B C DDD FOO BAR

如果我用cmd "$(< file)"我得到

cmd "AAA B C DDD FOO BAR"

我如何让每行都被正确地对待一个参数?


Answers:


26

便携性:

set -f              # turn off globbing
IFS='
'                   # split at newlines only
cmd $(cat <file)
unset IFS
set +f

或者使用子外壳使IFSand选项更改为本地:

( set -f; IFS='
'; exec cmd $(cat <file) )

Shell对不在双引号中的变量或命令替换的结果执行字段拆分和文件名生成。因此,您需要使用来关闭文件名的生成set -f,并使用IFS来配置字段拆分,以仅使换行符分隔字段。

bash或ksh构造并没有太多好处。您可以将IFS函数设为本地,但不能设为set -f

在bash或ksh93中,如果需要将字段传递给多个命令,则可以将它们存储在数组中。您在构建阵列时需要控制扩展。然后"${a[@]}"扩展到数组的元素,每个单词一个。

set -f; IFS=$'\n'
a=($(cat <file))
set +f; unset IFS
cmd "${a[@]}"

10

您可以使用临时数组来执行此操作。

设定:

$ cat input
AAA
A B C
DE F
$ cat t.sh
#! /bin/bash
echo "$1"
echo "$2"
echo "$3"

填充数组:

$ IFS=$'\n'; set -f; foo=($(<input))

使用数组:

$ for a in "${foo[@]}" ; do echo "--" "$a" "--" ; done
-- AAA --
-- A B C --
-- DE F --

$ ./t.sh "${foo[@]}"
AAA
A B C
DE F

如果没有该临时变量,就无法找到解决方法-除非IFS更改对而言并不重要cmd,在这种情况下:

$ IFS=$'\n'; set -f; cmd $(<input) 

应该这样做。


IFS总是让我感到困惑。 IFS=$'\n' cmd $(<input)不起作用。 IFS=$'\n'; cmd $(<input); unset IFS确实有效。为什么?我想我会使用(IFS=$'\n'; cmd $(<input))
旧版Pro

6
@OldPro IFS=$'\n' cmd $(<input)不起作用,因为它仅IFS在的环境中设置cmd$(<input)在执行对的分配之前,将其展开以形成命令IFS
吉尔斯(Gillles)“所以-别再邪恶了”

8

看起来规范的方式bash是这样的

unset args
while IFS= read -r line; do 
    args+=("$line") 
done < file

cmd "${args[@]}"

或者,如果您的bash版本具有mapfile

mapfile -t args < filename
cmd "${args[@]}"

我可以在mapfile和while读取循环与单线之间找到的唯一区别

(set -f; IFS=$'\n'; cmd $(<file))

是前者会将空白行转换为空参数,而单行代码将忽略空白行。在这种情况下,无论如何我还是更喜欢单线行为,因此紧凑的双倍奖励。

我会使用,IFS=$'\n' cmd $(<file)但是它不起作用,因为$(<file)IFS=$'\n'生效之前会被解释为形成命令行。

尽管在我的情况下不起作用,但我现在了解到,有很多工具都支持终止行,null (\000)newline (\n)在处理文件名(例如文件名)时,终止符确实使终止行变得更容易:

find / -name '*.config' -print0 | xargs -0 md5

将完全限定的文件名列表作为md5的参数提供,而不会出现任何乱码或插值等情况。这导致了非内置解决方案

tr "\n" "\000" <file | xargs -0 cmd

尽管这也忽略了空行,但是它确实捕获了只有空格的行。


使用cmd $(<file)不带引号的值(使用bash拆分单词的能力)始终是冒险的选择。如果有任何一行*,shell会将其扩展为文件列表。

3

您可以使用内置的bash mapfile将文件读入数组

mapfile -t foo < filename
cmd "${foo[@]}"

或者,未经测试,xargs可能会这样做

xargs cmd < filename

从mapfile文档中:“ mapfile不是常见的或可移植的Shell功能”。确实我的系统不支持它。 xargs也无济于事。
老职业

您需要xargs -dxargs -L
詹姆斯杨曼

@James,不,我没有-d选择,xargs -L 1每行运行一次命令,但仍然在空白处分割args。
老职业

1
@OldPro,您确实要求“仅使用bash内置插件来实现此目标的方法”,而不是“常见或可移植的shell功能”。如果您的bash版本太旧,可以更新吗?
格伦·杰克曼

mapfile对我来说非常方便,因为它可以将空行作为数组项,而该IFS方法无法做到。IFS将连续的换行符视为单个定界符...感谢您介绍它,因为我不知道该命令(尽管基于OP的输入数据和预期的命令行,看来他实际上是想忽略空行)。
Peter.O 2012年

0
old=$IFS
IFS='  #newline
'
array=`cat Submissions` #input the text in this variable
for ...  #use parts of variable in the for loop
... 
done
IFS=$old

我能找到的最好方法。正常工作。


如果设置IFS为空格,为什么它会起作用,但问题是不要在空格上分割?
拉尔夫·弗里德尔(RalfFriedl),

0

文件

在换行符上分割文件的最基本的循环(便携式)是:

#!/bin/sh
while read -r line; do            # get one line (\n) at a time.
    set -- "$@" "$line"           # store in the list of positional arguments.
done <infile                      # read from a file called infile.
printf '<%s>' "$@" ; echo         # print the results.

将打印:

$ ./script
<AAA><A B C><DE F>

是的,默认值为IFS = spacetabnewline

为什么有效

  • Shell将使用IFS将输入分为几个变量。由于只有一个变量,因此外壳程序不执行拆分。因此,无需更改IFS
  • 是的,前导空格和尾随空格/制表符已被删除,但是在这种情况下似乎没有问题。
  • 没有,没有通配,因为没有扩展做不带引号的。因此,set -f不需要。
  • 使用(或需要)的唯一数组是类似数组的位置参数。
  • -r(原)选择是避免去除大部分反斜杠。

这将不是是否需要拆分和/或通配符工作。在这种情况下,需要更复杂的结构。

如果您需要(仍可移植)以:

  • 避免删除前导和尾随空格/制表符,请使用: IFS= read -r line
  • 在某些字符上将行分割为var,请使用:IFS=':' read -r a b c

文件分割为其他字符(不可移植,可与ksh,bash,zsh一起使用):

IFS=':' read -d '+' -r a b c

扩张

当然,您的问题的标题是关于在换行符上分割命令执行以避免在空格上分割。

从shell拆分的唯一方法是不带引号而展开:

echo $(< file)

这由IFS的值控制,并且在未引用的扩展名上也应用了glob。要进行这项工作,您需要:

  • 将IFS设置为only new line,在newline上拆分。
  • 取消设置globbing shell选项set +f

    设置+ f IFS =''cmd $(<文件)

当然,这会更改IFS的值以及脚本其余部分的通配符。

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.