在Bash数组上使用参数替换


8

我有要读入Bash数组的file.txt文件。然后,我需要删除空格,双引号以及每个条目中除第一个逗号以外的所有内容。这是我走了多远:

$ cat file.txt
10,this
2 0 , i s
30,"all"
40,I
50,n,e,e,d,2
60",s e,e"

$ cat script.sh
#!/bin/bash
readarray -t ARRAY<$1
ARRAY=( "${ARRAY[@]// /}" )
ARRAY=( "${ARRAY[@]//\"/}" )
for ELEMENT in "${ARRAY[@]}";do
    echo "|ELEMENT|$ELEMENT|"
done

$ ./script.sh file.txt
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,n,e,e,d,2|
|ELEMENT|60,se,e|

除逗号情况外,哪种效果都很好。我知道有很多方法可以给这只猫换皮,但是由于这是其中较大的脚本,所以我真的很想使用参数替换到达此处:

|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

通过参数替换可以做到这一点吗?


3
有什么理由需要将文本保留在数组中,为什么不能让eg awksed进行数据处理呢?
库萨兰达

@Jeff-遍历数组将是我正在研究的较大脚本中实现的噩梦。
乔恩·雷德

3
@JonRed我不知道您在做什么,因此您很可能没有选择的余地,但是通常,当您发现自己在shell中进行如此复杂的字符串杂技时,这很好地表明了您应该使用实际的编程语言。Shell并非设计为一种编程语言,虽然可以用作一种编程语言,但对于更复杂的事情来说,这并不是一个好主意。我强烈建议您考虑改用perl或python或任何其他脚本语言。
terdon

@terdon有趣的是,在阅读这篇文章之前,我已经对我的同事说了几乎完全相同的话。我基本上说过,这是此脚本的最终版本,任何进一步的要求都将需要在Perl中进行重写。所以,我绝对同意
Jon Red

Answers:


9

我将删除需要删除的东西,sed 然后再加载到数组中(还要注意小写的变量名,通常最好避免在shell脚本中使用大写的变量):

#!/bin/bash
readarray -t array< <(sed 's/"//g; s/  *//g; s/,/"/; s/,//g; s/"/,/' "$1")
for element in "${array[@]}";do
    echo "|ELEMENT|$element|"
done

这将在您的示例文件上产生以下输出:

$ foo.sh file 
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

如果确实必须使用参数替换,请尝试以下操作:

#!/bin/bash
readarray -t array< "$1"
array=( "${array[@]// /}" )
array=( "${array[@]//\"/}" )
array=( "${array[@]/,/\"}" )
array=( "${array[@]//,/}" )
array=( "${array[@]/\"/,}" )

for element in "${array[@]}"; do
    echo "|ELEMENT|$element|"
done

1
@JonRed我添加了一个带有参数替换的版本,但它复杂,繁琐且丑陋。在shell中执行这种操作很少是一个好主意。
terdon

1
请注意,如果您同时删除了空格和双引号,则可以使用这些字符来代替RANDOMTEXTTHATWILLNEVERBEINTHEFILE
库萨兰达

1
@Kusalananda是的,我刚刚读了你的答案。应该想到了!谢谢:)
terdon

直接回答问题,说明为什么我的首选解决方案不理想,并提供最可行的替代方案。您赢了,最好的答案。
乔恩·雷德

10

据我所知,无需将其读入bash数组即可创建该输出:

$ sed 's/[ "]//g; s/,/ /; s/,//g; s/ /,/; s/.*/|ELEMENT|&|/' <file
|ELEMENT|10,this|
|ELEMENT|20,is|
|ELEMENT|30,all|
|ELEMENT|40,I|
|ELEMENT|50,need2|
|ELEMENT|60,see|

sed表达式删除空格和双引号,将第一个逗号替换为一个空格(此时字符串中没有其他空格),删除所有其他逗号,恢复第一个逗号,并添加和追加多余的数据。

或者,使用GNU sed

sed 's/[ "]//g; s/,//2g; s/.*/|ELEMENT|&|/' <file

(标准sed不支持命令的2g作为标志的组合s)。


1
与GNU sed的,您可以使用's/,//2g删除逗号,开始第2
格伦·杰克曼

2
并且,最后两个s ///命令可以是s/.*/|ELEMENT|&|/sed,但这样做可能会更费力。
格伦杰克曼

1
@glennjackman可能,但是看起来很整洁。
库沙兰丹

是的,这是较大脚本的一部分。该数组是必需的,而不仅仅是输出。因此,我对参数替换感兴趣。我可以用它来遍历数组,但这将是一场噩梦。Terndon使用sed提供了一个无循环的解决方案,如果参数替换是不可行的话,我可能会退而求其次。
乔恩·雷德

但是,如果我不局限于使用数组,那将是最佳解决方案。
乔恩·雷德

9
ELEMENT='50,n,e,e,d,2'
IFS=, read -r first rest <<<"$ELEMENT"
printf "%s,%s\n" "$first" "${rest//,/}"
50,need2

摆脱使用ALLCAPS变量名的习惯。您最终将与关键的“系统”变量(如PATH)发生冲突,并破坏您的代码。


不是参数替换。但是,我没有意识到ALLCAPS变量名在Bash中是个坏习惯。您提出了一个很好的观点,可以肯定地确认了这一点。感谢您改善我的风格!:)
乔恩·雷德

1
我已经回答了该人在哪里写的问题,PATH=something; ls $PATH然后想知道该ls: command not found错误。
格伦·杰克曼

1
在所有大写字母中都有将近一百个内置变量被命名(单击此手册页链接)以查看...
Jeff Schaller

8

[这本质上是glenn jackmann的答案的更全面开发的版本]

使用第一个逗号作为分隔符,从剥离的键和值构建关联数组:

declare -A arr
while IFS=, read -r k v; do arr["${k//[ \"]}"]="${v//[ ,\"]}"; done < file.txt
for k in "${!arr[@]}"; do 
  printf '|ELEMENT|%s,%s|\n' "$k" "${arr[$k]}"
done
|ELEMENT|20,is|
|ELEMENT|10,this|
|ELEMENT|50,need2|
|ELEMENT|40,I|
|ELEMENT|60,see|
|ELEMENT|30,all|

6

您可以遍历数组并使用中间变量:

for((i=0; i < "${#ARRAY[@]}"; i++))
do
  rest="${ARRAY[i]#*,}"
  ARRAY[i]="${ARRAY[i]%%,*}","${rest//,/}"
done

这将分配给rest第一个逗号之后的部分;然后,我们将三部分连接回原始变量:

  • 第一个逗号之前的部分
  • 逗号
  • rest任何逗号的替换都一无所有

这是我的第一个想法,对于示例来说很简单,但这是较大脚本的一部分,在较大脚本中,数组很大,已经有循环,这将是一回事。这肯定可以,但是要在我正在从事的较大项目中实施非常麻烦。
乔恩·雷德

1
很公平; 我只是试图在限制范围内回答(仅参数扩展)。
杰夫·谢勒
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.