csv 将Bash数组转换为分隔字符串

olhwl3o2  于 2023-09-27  发布在  其他
关注(0)|答案(4)|浏览(84)

我想知道以下内容:
1.为什么这个不工作的例子不工作。
1.是否有比工作示例中给出的方法更干净的方法。

  • 非工作示例 *
> ids=(1 2 3 4);echo ${ids[*]// /|}
1 2 3 4
> ids=(1 2 3 4);echo ${${ids[*]}// /|}
-bash: ${${ids[*]}// /|}: bad substitution
> ids=(1 2 3 4);echo ${"${ids[*]}"// /|}
-bash: ${"${ids[*]}"// /|}: bad substitution
  • 工作示例 *
> ids=(1 2 3 4);id="${ids[@]}";echo ${id// /|}
1|2|3|4
> ids=(1 2 3 4); lst=$( IFS='|'; echo "${ids[*]}" ); echo $lst
1|2|3|4

在上下文中,sed命令中用于进一步分析的分隔字符串。

e4yzc0pl

e4yzc0pl1#

数组到bash中的字符串

1.数组到字符串,使用$IFS

因为括号是用来分隔数组,而不是字符串:

ids="1 2 3 4";echo ${ids// /|}
1|2|3|4

一些样品:用两个字符串填充$idsa bc d

ids=("a b" "c d")

echo ${ids[*]// /|}
a|b c|d

IFS='|';echo "${ids[*]}";IFS=$' \t\n'
a b|c d

最后:

IFS='|';echo "${ids[*]// /|}";IFS=$' \t\n'
a|b|c|d

其中数组被组装,由$IFS的第一个字符分隔,但在数组的每个元素中用|替换空格。
当您这样做时:

id="${ids[@]}"

你把arrayids通过一个空格合并后的字符串构建转移到一个类型为string的新变量中。

注意:"${ids[@]}"给予一个 * 空格分隔 * 的字符串时,"${ids[*]}"(使用星星*而不是at符号@)将呈现一个由$IFS的第一个字符分隔的字符串。

man bash说:

man -Len -Pcol\ -b bash | sed -ne '/^ *IFS /{N;N;p;q}'
   IFS    The  Internal  Field  Separator  that  is used for word splitting
          after expansion and to split  lines  into  words  with  the  read
          builtin command.  The default value is ``<space><tab><newline>''.

使用$IFS

printf "%q\n" "$IFS"
$' \t\n'

字面上是一个space,一个tabulation(意思是或) 一个line-feed。第一个字符是空格。*的使用将与@的使用相同。

但是

{
    IFS=: read -a array < <(echo root:x:0:0:root:/root:/bin/bash)
    
    echo 1 "${array[@]}"
    echo 2 "${array[*]}"
    OIFS="$IFS" IFS=:
    echo 3 "${array[@]}"
    echo 4 "${array[*]}"
    IFS="$OIFS"
}
1 root x 0 0 root /root /bin/bash
2 root x 0 0 root /root /bin/bash
3 root x 0 0 root /root /bin/bash
4 root:x:0:0:root:/root:/bin/bash

注意:IFS=: read -a array < <(...)行将使用:作为分隔符,不会永久设置$IFS。这是因为输出行#2将空格作为分隔符。

1.1使用函数 localize$IFS

只打印数组

printArry() {
    local IFS="$1"
    shift
    echo "$*"
}        

printArry @ "${ids[@]}"
a b@c d

或者合并数组。

mergeArry() {
    local IFS="$1"
    local -n _array_to_merge=$2
    _array_to_merge=("${_array_to_merge[*]}")
}

declare -p ids
declare -a ids=([0]="a b" [1]="c d")

mergeArry '#' ids
declare -p ids
declare -a ids=([0]="a b#c d")

2.字符串数组到字符串数组([@] vs [*]

以下两者之间存在显著差异:

  • "$@""${var[@]}"生成字符串数组
  • "$*""${var[*]}"生成唯一字符串

仔细阅读:man '-Pless +/Special\ Parameters' bash
为此,我将引号每个参数,以便不会在 * 命令行扩展 * 时被$IFS拆分,使用双引号允许 * 变量扩展 *。

printf '<< %s >>\n' "${ids[@]// /|}"
<< a|b|c >>
<< d|e|f >>
<< g|h|i >>
printf '<< %s >>\n' "${ids[*]// /|}"
<< a|b|c d|e|f g|h|i >>

其中:

  • 每个字符串中的所有空格都被替换为竖线
  • 所有字符串合并为一个字符串,由第一个$IFS字符分隔。
( IFS='@'; printf '<< %s >>\n' "${ids[*]// /|}" )
<< a|b|c@d|e|f@g|h|i >>

注意:${var// /something}会用 something 替换每一个 spaces,但是${var[*]}只会使用一个第一个字符来合并数组:

( IFS='FOO'; printf '<< %s >>\n' "${ids[*]// / BAR }" )
<< a BAR b BAR cFd BAR e BAR fFg BAR h BAR i >>

是的:通过使用${var// / ... },您可以将1空格替换为您想要的任何内容,包括更多空格。

3.数组转字符串,使用printf

正如我们所看到的,使用$IFS仅限于1个字符。如果需要在字段之间插入更多字符。使用printf

ids=("a b" "c d")
sep=" long separator "
printf -v string "%s$sep" "${ids[@]}"
echo "${string%$sep}"
a b long separator c d

3.1数组到字符串,通过printf,转换成函数

printArry() {
    local sep=$1 string
    shift
    printf -v string "%s$sep" "$@"
    echo "${string%$sep}"
}        

printArry ' long separator ' "${ids[@]}"
a b long separator c d

或者合并数组。

mergeArry() {
    local sep=$1 string
    local -n _array_to_merge=$2
    printf -v string "%s$sep" "${_array_to_merge[@]}"
    _array_to_merge=("${string%$sep}")
}

mergeArry ' another separator ' ids
declare -p ids
declare -a ids=([0]="a b another separator c d")
mv1qrgav

mv1qrgav2#

您也可以使用printf,无需任何外部命令或操作IFS:

ids=(1 2 3 4)                     # create array
printf -v ids_d '|%s' "${ids[@]}" # yields "|1|2|3|4"
ids_d=${ids_d:1}                  # remove the leading '|'
iyzzxitl

iyzzxitl3#

您的第一个问题已经在F. Hauri's answer中得到了解决。下面是连接数组元素的规范方法:

ids=( 1 2 3 4 )
IFS=\| eval 'lst="${ids[*]}"'

有些人会大声说eval是邪恶的,但它在这里是完全安全的,这要归功于单引号。这只有好处:没有子shell,IFS没有全局修改,它不会修剪尾随的换行符,它非常简单。

x6h2sr28

x6h2sr284#

一个将参数数组连接到分隔字符串的实用函数:

#!/usr/bin/env bash

# Join arguments with delimiter
# @Params
# $1: The delimiter string
# ${@:2}: The arguments to join
# @Output
# >&1: The arguments separated by the delimiter string
array::join() {
  (($#)) || return 1 # At least delimiter required
  local -- delim="$1" str IFS=
  shift
  str="${*/#/$delim}" # Expands arguments with prefixed delimiter (Empty IFS)
  printf '%s\n' "${str:${#delim}}" # Echo without first delimiter
}

declare -a my_array=( 'Paris' 'Berlin' 'London' 'Brussel' 'Madrid' 'Oslo' )

array::join ', ' "${my_array[@]}"
array::join '*' {1..9} | bc # 1*2*3*4*5*6*7*8*9=362880 Factorial 9

declare -a null_array=()

array::join '== Ultimate separator of nothing ==' "${null_array[@]}"

输出量:

Paris, Berlin, London, Brussel, Madrid, Oslo
362880

现在有了Bash 4.2+的nameref变量,不再需要使用子shell输出捕获。

#!/usr/bin/env bash

if ((BASH_VERSINFO[0] < 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[0] < 2)))
then
  printf 'Bash version 4.2 or above required for nameref variables\n' >&2
  exit 1
fi

# Join arguments with delimiter
# @Params
# $1: The variable reference to receive the joined output
# $2: The delimiter string
# ${@:3}: The arguments to join
# @Output
array::join_to() {
  (($# > 1)) || return 1 # At least nameref and delimiter required
  local -n out="$1"
  local -- delim="$2" str IFS=
  shift 2
  str="${*/#/$delim}" # Expands arguments with prefixed delimiter (Empty IFS)
  # shellcheck disable=SC2034 # Nameref variable
  out="${str:${#delim}}" # Discards prefixed delimiter
}

declare -g result1 result2 result3
declare -a my_array=( 'Paris' 'Berlin' 'London' 'Brussel' 'Madrid' 'Oslo' )

array::join_to result1 ', ' "${my_array[@]}"
array::join_to result2 '*' {1..9}
result2=$((result2)) # Expands arythmetic expression

declare -a null_array=()

array::join_to result3 '== Ultimate separator of nothing ==' "${null_array[@]}"

printf '%s\n' "$result1" "$result2" "$result3"

相关问题