使用带有嵌入空格的参数(例如文件名)调用 bash 脚本中的命令

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/16358580/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-18 05:20:27  来源:igfitidea点击:

Calling commands in bash script with parameters which have embedded spaces (eg filenames)

linuxbash

提问by John Dibling

I am trying to write a bash script which does some processing on music files. Here is the script so far:

我正在尝试编写一个对音乐文件进行一些处理的 bash 脚本。这是到目前为止的脚本:

#!/bin/bash

SAVEIFS=$IFS
IFS=printf"\n
[johnd:/tmp/tunes] 2 $ ./test.sh 
./test.sh: line 9: syntax error near unexpected token `$(f)'
./test.sh: line 9: `    $arr=($(f))'
[johnd:/tmp/tunes] 2 $ 
" find `pwd` -iname "*.mp3" -o -iname "*.flac" | while read f do echo "$f" $arr=($(f)) exiftool "${arr[@]}" done IFS=$SAVEIFS

This fails with:

这失败了:

while read -r -d '' f; do
    echo "$f" # For debugging?
    arr+=("$f")
done < <(find . -iname "*.mp3" -o -iname "*.flac" -print0)
exiftool "${arr[@]}"

I have tried many different incantations, none of which have worked. The bottom line is I'm trying to call a command exiftool, and one of the parameters of that command is a filename which may contain spaces. Above I'm trying to assign the filename $fto an array and pass that array to exiftool, but I'm having trouble with the construction of the array.

我试过很多不同的咒语,都没有奏效。最重要的是我正在尝试调用 command exiftool,该命令的参数之一是可能包含空格的文件名。上面我试图将文件名分配$f给一个数组并将该数组传递给exiftool,但是我在构建数组时遇到了麻烦。

Immediate question is, how do I construct this array? But the deeper question is how, from within a bash script, do I call an external command with parameters which may contain spaces?

直接的问题是,我如何构造这个数组?但更深层次的问题是,如何从 bash 脚本中调用带有可能包含空格的参数的外部命令?

回答by michaelb958--GoFundMonica

You actually did have the call-with-possibly-space-containing-arguments syntax right (program "${args[@]}"). There were several problems, though.

您实际上确实拥有正确的 call-with-possably-space-包含-arguments 语法 ( program "${args[@]}")。但是,有几个问题。

Firstly, $(foo)executes a command. If you want a variable's value, use $fooor ${foo}.

首先,$(foo)执行一个命令。如果您想要一个变量的值,请使用$foo${foo}

Secondly, if you want to append something onto an array, the syntax is array+=(value)(or, if that doesn't work, array=("${array[@]}" value)).

其次,如果您想将某些内容附加到数组中,则语法为array+=(value)(或者,如果这不起作用,则为array=("${array[@]}" value))。

Thirdly, pleaseseparate filenames with \0whenever possible. Newlines are all well and good, but filenames can contain newlines.

第三,\0尽可能将文件名与. 换行符都很好,但文件名可以包含换行符。

Fourthly, readtakes the switch -d, which can be used with an empty string ''to specify \0as the delimiter. This eliminates the need to mess around with IFS.

第四,read采用 switch -d,它可以与空字符串''一起使用\0作为分隔符。这消除了对IFS.

Fifthly, be careful when piping into whileloops - this causes the loop to be executed in a subshell, preventing variable assignments inside it from taking effect outside. There is a way to get around this, however - instead of piping (command | while ... done), use process substitution (while ... done < <(command)).

第五,当管道进入while循环时要小心——这会导致循环在子外壳中执行,防止其内部的变量赋值在外部生效。但是,有一种方法可以解决此问题 -command | while ... done使用进程替换 ( while ... done < <(command))代替管道( )。

Sixthly, watch your process substitutions - there's no need to use $(pwd)as an argument to a command when .will do. (Or if you really must have full paths, try quoting the pwdcall.)

第六,注意你的进程替换 - 不需要将$(pwd)何时用作命令的参数.。(或者,如果您确实必须拥有完整路径,请尝试引用pwd调用。)

tl;dr

tl;博士

The script, revised:

修改后的脚本:

find . -iname "*.mp3" -o -iname "*.flac" -exec exiftool {} +
# Much shorter!

Another way

其它的办法

Leveraging find's full capabilities:

利用find的全部功能:

while read -r -d '' f; do
    echo "$f" # For debugging?
    arr+=("$f")
done < <(find . -iname "*.mp3" -o -iname "*.flac" -print0)
# Warning: somewhat misleading syntax highlighting ahead
newfilename="$(exiftool "${arr[@]}")"
newfilename="$(manipulate "$newfilename")"
cp -- "$some_old_filename" "$newfilename"

Edit 1

编辑 1

So you need to save the output of exiftool, manipulate it, then copy stuff? Try this:

所以你需要保存 的输出exiftool,操作它,然后复制东西?尝试这个:

shopt -s globstar nullglob
a=( **/*.{mp3,flac} )
exiftool "${a[@]}"

You probably will need to change that last bit - I've never used exiftool, so I don't know precisely what you're after (or how to do it), but that should be a start.

您可能需要更改最后一点 - 我从未使用过exiftool,所以我不确切知道您在追求什么(或如何去做),但这应该是一个开始。

回答by glenn Hymanman

You can do this just with bash:

你可以只用 bash 做到这一点:

##代码##

This probably works too: exiftool **/*.{mp3,flac}

这可能也有效: exiftool **/*.{mp3,flac}