在bash中循环值对[重复]
Posted
技术标签:
【中文标题】在bash中循环值对[重复]【英文标题】:Looping over pairs of values in bash [duplicate] 【发布时间】:2015-04-27 19:40:06 【问题描述】:我有 10 个文本文件,我想 paste
每个文件及其对,这样我总共有 5 个文件。
我尝试了以下方法:
for i in 4_1 5_1 6_1 7_1 8_1
do
for j in 4_2 5_2 6_2 7_2 8_2
do
paste $i.txt $j.txt > $i.$j.txt
done
done
但是,此代码组合了所有可能的组合,而不仅仅是组合匹配的对。
所以我希望将文件4_1.txt
与4_2.txt
、5_1.txt
与5_2.txt
等配对。
【问题讨论】:
你需要遍历前缀和后缀而不是完整的文件名。 您得到的行为与嵌套的for
循环在任何 语言中的行为相同;没有什么特定于 bash 的。
每次我想做一些相对简单的事情,比如迭代成对的值时,Bash 都会让工作负载看起来像是难以克服的史诗。因此,当需要比 shell 调用列表更复杂的东西时,我不会使用 bash。
最初的问题不一定是关于数组的。这只是关于配对。 (这里的一些答案也是如此)。
【参考方案1】:
以上对我不起作用,但以下确实从有序列表中成对读取值
(可以多对添加额外的“阅读行”:-)
while read x; do
read y
echo "$x $y"
done << '___HERE'
X1
Y1
X2
Y2
X3
Y3
___HERE
生产
X1 Y1
X2 Y2
X3 Y3
【讨论】:
你也可以while read x && read y; do
,我发现这让读者更容易理解你每次迭代消耗两个项目。
这是一种比上述任何方法都更简单、更好的答案。
这是越野车。 read x
和 read y
在不使用 -r
参数时使用文字反斜杠损坏数据; echo $x $y
以不同的方式破坏数据,如 BashPitfalls #14 中所述。
这不使用 Bash-only 功能(尽管你会想要使用read -r
如上所述),因此可以移植到任何与 Bourne 兼容的 shell。【参考方案2】:
迄今为止最简单的:
for i in "1 a" "2 b" "3 c"; do a=( $i ); echo "$a[1]"; echo "$a[0]"; done
a
1
b
2
c
3
【讨论】:
为什么这被否决了?它适用于 bash ver.3 及更高版本,并且足够简单,不需要使用 heredoc。 比其他解决方案更好 简单好用,谢谢。【参考方案3】:您可以使用关联数组:
animals=(dog cat mouse)
declare -A size=(
[dog]=big
[cat]=medium
[mouse]=small
)
declare -A sound=(
[dog]=barks
[cat]=purrs
[mouse]=cheeps
)
for animal in "$animals[@]"; do
echo "$animal is $size[$animal] and it $sound[$animal]"
done
这允许您遍历对、三元组等。致谢:最初的想法来自@CharlesDuffy-s 的回答。
【讨论】:
我不是建议(并演示)了这种技术吗? 有效问题。最初我从一个有点不同的答案开始,你的答案是“学分”。但是在 9 月,我有些原因决定重构所有内容并实际上摆脱了一些东西。我现在已经恢复了原来的答案。【参考方案4】:有一种常见的模式,即您拥有一对文件,其中一对文件的一个名称可以很容易地从另一个文件中派生出来。如果您知道的文件名称为 X,而另一个文件名称为 Y,则您有以下常见用例。
对于重命名,Y 是 X,移除了扩展名和/或添加了日期戳。 对于转码,Y 是 X,具有不同的扩展名,可能还有不同的目录。 对于许多数据分析任务,X 和 Y 共享文件名的某些部分,但具有不同的参数或扩展名。所有这些都适用于相同的粗略代码框架。
for x in path/to/base*.ext; do
dir=$x%/* # Trim trailing file name, keep dir
base=$x##*/ # Trim any leading directory
# In this case, $y has a different subdirectory and a different extension
y=$dir%/to/from/$base%.ext.newext
# Maybe check if y exists? Or doesn't exist?
if [ -e "$y" ]; then
echo "$0: $y already exists -- skipping" >&2
continue
fi
mv or ffmpeg or awk or whatever "$x" and "$y"
done
这里的关键是观察到y
可以通过一些简单的变量替换从x
派生而来。因此,您循环遍历 x
值,并找出循环内对应的 y
值。
在这里,我们使用了 shell 的内置 $variable#prefix
和 $variable%suffix
运算符来返回变量的值,其中任何前导 prefix
或尾随 suffix
分别被修剪掉。 (还有 ##
和 %%
来匹配最长的,而不是最短的,可能的匹配。#
或 %
之后的表达式是一个常规的 shell glob 模式。)这些通常应该是你所需要的,尽管您经常看到 sed
或 awk
脚本,即使是对于这项琐碎的工作(实际上您通常应该尽量避免外部过程),当然还有更苛刻的转换。
如果您需要循环遍历分散在不同目录中的 x
文件,那么循环应该以类似的方式开始
find dir1 dir2 etc/and/so/forth -type f -name 'x-files*.ext' -print |
while IFS='' read -r x; do
:
类似问题中常见的问题是未能正确引用$x
和$y
的答案。通常,任何包含文件名的变量都应该用双引号括起来。
在 X 和 Y 不相关的情况下,一种常见的解决方案是遍历包含映射的 here 文档:
while read -r x y; do
: stuff with "$x" and "$y"
done <<'____HERE'
first_x_value first_y_value
another_x corresponding_y
random surprise
____HERE
【讨论】:
我真的不能一本正经地写“X档案”。对不起。 这不使用任何严格的 Bash-only 功能,但“最长匹配”参数扩展##
和 %%
并非在所有 shell 中都可用。在最坏的情况下,可能使用sed
进行替换,或者其他什么。【参考方案5】:
我同意 fedorqui 目前就当前提出的问题提出的答案。下面给出的只是提供一些更一般的答案。
一种更通用的方法(对于 bash 4.0 或更高版本)是将您的对存储在关联数组中:
declare -A pairs=( [4_1]=4_2 [5_1]=5_2 [6_1]=6_2 [7_1]=7_2 [8_1]=8_2 )
for i in "$!pairs[@]"; do
j=$pairs[$i]
paste "$i.txt" "$j.txt" >"$i.$j.txt"
done
另一个(与旧版本的 bash 兼容)是使用多个常规数组:
is=( 4_1 5_1 6_1 7_1 8_1 )
js=( 4_2 5_2 6_2 7_2 8_2 )
for idx in "$!is[@]"; do
i=$is[$idx]
j=$js[$idx]
paste "$i.txt" "$j.txt" >"$i.$j.txt"
done
【讨论】:
我很失望没有更精简的版本,比如循环一个列表,然后将每个元素分成两部分 这个问题指定了两个不同的列表。做另一件事很容易;这不是这里的 OP 所要求的。 @CharlesDuffy 我来这里是因为我在 Google 中输入了“iterate in pairs bash”,而不是因为我关心 OP 的问题。 @Boris, ...这证明这个问题应该被编辑为具有与另一个相关问题消除歧义的标题——如果有两个不同的问题可以用相同的描述单词,它们的标题应该明确哪个是哪个。【参考方案6】:如果你想使用一个变量并对其执行和操作,你只需要使用一个循环:
for file in 4 5 6 7 8
do
paste "$file_1" "$file_2"
done
这样就可以了
paste 4_1 4_2
paste 5_1 5_2
...
【讨论】:
甚至paste "$file"_1,2
看,妈!没有循环:printf '%s %s\n' 4..8_1,2 | xargs -I cmd paste cmd
但是,如果您有需要引用或转义的重要文件名,则在未引用的值上运行 xargs
很快就会失败。
它不使用 Bash-only 功能,因此可以移植到任何与 Bourne 兼容的 shell。以上是关于在bash中循环值对[重复]的主要内容,如果未能解决你的问题,请参考以下文章