【问题标题】:Bash text file editing/modifyingBash 文本文件编辑/修改
【发布时间】:2017-11-10 12:38:08
【问题描述】:

我有一个要修改的文本文件。我正在使用具有以下形式的行的输入文件

(y+1/4,-x+1/2,z+3/4)

并尝试将其更改为

   0     1     0    -1     0     0     0     0     1    1 / 4    1 / 2    3 / 4

我目前可以达到这一点

   0     1     0 1/4    -1     0     0 1/2     0     0     1 3/4

使用

#!bin/bash
filename="227.dat"
sed -i 's/(/  /g' $filename
sed -i 's/)//g' $filename
sed -i 's/,/    /g' $filename
sed -i 's/-x/-1     0     0/g' $filename
sed -i 's/x/ 1     0     0/g' $filename
sed -i 's/-y/ 0    -1     0/g' $filename
sed -i 's/y/ 0     1     0/g' $filename
sed -i 's/-z/ 0     0    -1/g' $filename
sed -i 's/z/ 0     0     1/g' $filename
sed -i '/+/! s/$/    0 \/ 1    0 \/ 1    0 \/ 1/' $filename

while ((i++)); read -r line; do
  if [[ $line == *[+]* ]]
    then
      sed -i 's/+/ /g' $filename
      echo $i
  fi
done < "$filename"

echo $i 的原因是为了看到它正确地给出了行号,我想也许我可以将它用于那些特定行的命令。我正在做这种转换,因为我们在创建晶体结构时使用的代码需要最后带有分数的矢量符号,而不是 x、y、z 符号。我已经知道这不是“最漂亮”或最简单的解决方案,但我对这一切都很陌生,这是我迄今为止能够拼凑起来的。有什么建议吗?

【问题讨论】:

  • 每次运行 sed -i 时,这确实非常昂贵(您正在启动一个新程序,让它创建一个新的输出文件,然后将该输出文件重命名为旧文件......对于每个单一变化,在目前的表述中)。最好运行 一个 sed 实例,它可以一次性完成所有操作 - 或者首先使用 native string manipulation 而不是外部工具。
  • 例如,如果您可以编写一个匹配您的输入流的正则表达式,您可以使用[[ $line =~ $regex ]] 将正则表达式的组放入BASH_REMATCH 数组中——那么您只有 一个 echo 命令将这些组按正确的顺序排列,很容易。
  • 现在不容易的是准确解析现有代码的工作原理,从而弄清楚你声明的输入和声明的输出之间的预期关系是什么——使得编写任何从头开始的重新实现变得不必要地困难,但是在当前情况下这样做可能是显而易见的正确事情。
  • 我知道这不是最好的方法,但我只需要一些可行的方法,并且可以担心以后对其进行优化。我不确定什么是正则表达式(如前所述,我对这一切都很陌生)。至于输入/输出关系,我将 x,y,z 分别更改为 100,010,001 并且需要将任何添加的分数移动到行尾,所有这些都显示为间距。
  • 这是您的第三个几乎相同的问题,我仍然很难理解您要做什么。你能描述一下潜在的问题吗?我知道这与单位向量有关。但是您的输入如何映射到您的预期输出?

标签: bash shell


【解决方案1】:

这是一种可以简化解析的方法。使用设置为所有可能的分隔符和您不关心的字符的 IFS 将每一行读入一个数组:

while IFS=$'\(\)+,' read -ra line; do
    for i in 1 3 5; do
        case "${line[$i]}" in
            x) printf "%s\t%s\t%s\t" 1 0 0 ;;
            y) printf "%s\t%s\t%s\t" 0 1 0 ;;
            z) printf "%s\t%s\t%s\t" 0 0 1 ;;
            -x) printf "%s\t%s\t%s\t" -1 0 0 ;;
            -y) printf "%s\t%s\t%s\t" 0 -1 0 ;;
            -z) printf "%s\t%s\t%s\t" 0 0 -1 ;;
        esac
    done
    for i in 2 4 6; do
        printf "%s\t" "${line[$i]}"
    done
    echo
done < "$filename"

【讨论】:

  • echo -ne 最好替换为printf '%s\t' "${line[$i]}"。请参阅POSIX spec for echo,它说 (1) 当存在-n 时,行为是完全未指定的; (2) 不允许其他选项(因此将-e 作为选项处理,而不是作为要打印的内容,主动违反规范); (3) printf 可用于模拟所有 echo 行为,并且是新代码的首选(请参阅应用程序使用部分)。
  • 另外请注意,即使 bash 也并不总是支持 echo -e,因为除了在输出上打印 -e 之外,它也会在 posixxpg_echo 标志已设置;并且可以通过编译时选项将其中一个或两个设置为默认值。
  • 谢谢查尔斯。这就解释了我在使用 echo -e 时偶尔遇到的可移植性问题。 :) 我已经相应地更新了我的答案。
【解决方案2】:
#!/usr/bin/env bash

filename="227.dat"

re='[(]y[+]([[:digit:]/]+),-x[+]([[:digit:]/]+),z[+]([[:digit:]/]+)[)]';
while IFS= read -r line; do
    if [[ $line =~ $re ]]; then
        printf '\t%s' \
            0 1 0 \
           -1 0 0 \
            0 0 1 \
            "${BASH_REMATCH[1]}" \
            "${BASH_REMATCH[2]}" \
            "${BASH_REMATCH[3]}";
        printf '\n';
    else
        echo "ERROR: $line does not match $re" 1>&2;
    fi;
done <"$filename"

...鉴于您的输入,返回:

   0       1       0       -1      0       0       0       0       1       1/4     1/2     3/4

...据我所知是正确的。


一种更复杂的方法,做出毫无根据的推断(鉴于问题本身缺乏细节和示例),可能如下所示:

#!/usr/bin/env bash
while IFS='(),' read -a pieces; do
  declare -A vars=( [x]=1 [y]=1 [z]=1 [x_sigil]='' [y_sigil]='' [z_sigil]='' )
  for piece in "${pieces[@]}"; do
    #                1   2      3   4
    if [[ $piece =~ (-?)([xyz])([+]([[:digit:]/]+))? ]]; then
      if [[ ${BASH_REMATCH[4]} ]]; then                 # only if there *are* digits
        vars[${BASH_REMATCH[2]}]=${BASH_REMATCH[4]}     # ...then store them.
      fi
      vars[${BASH_REMATCH[2]}_sigil]=${BASH_REMATCH[1]} # store - if applicable
    fi
  done
  printf '\t%s' \
    "0"                 "${vars[x_sigil]}1" 0 \
    "${vars[y_sigil]}1" 0                   0 \
    0                   0                   "${vars[z_sigil]}1" \
    "${vars[y]}"        "${vars[x]}"        "${vars[z]}"
  printf '\n'
done

鉴于对此答案的评论中提供的示例输入,输出为:

0   1   0   1   0   0   0   0   1   1   1   1
0   1   0   1   0   0   0   0   1   1   1   1
0   1   0   1   0   0   0   0   1   1   1   1
0   1   0   1   0   0   0   0   -1  3/4 1/4 1/2
0   1   0   -1  0   0   0   0   1   1/2 3/4 1/4
0   -1  0   1   0   0   0   0   1   1/4 1/2 3/4
0   -1  0   -1  0   0   0   0   -1  1   1   1
0   -1  0   -1  0   0   0   0   -1  1   1   1
0   -1  0   -1  0   0   0   0   -1  1   1   1
0   -1  0   -1  0   0   0   0   1   1/4 3/4 1/2
0   -1  0   1   0   0   0   0   -1  1/2 1/4 3/4
0   1   0   -1  0   0   0   0   -1  3/4 1/2 1/4
0   -1  0   -1  0   0   0   0   1   1/4 3/4 1/2
0   -1  0   -1  0   0   0   0   1   1/4 3/4 1/2
0   -1  0   -1  0   0   0   0   1   1/4 3/4 1/2
0   -1  0   -1  0   0   0   0   -1  1   1   1
0   -1  0   1   0   0   0   0   1   1/4 1/2 3/4
0   1   0   -1  0   0   0   0   1   1/2 3/4 1/4
0   1   0   1   0   0   0   0   -1  3/4 1/4 1/2
0   1   0   1   0   0   0   0   -1  3/4 1/4 1/2
0   1   0   1   0   0   0   0   -1  3/4 1/4 1/2
0   1   0   1   0   0   0   0   1   1   1   1
0   1   0   -1  0   0   0   0   -1  3/4 1/2 1/4
0   -1  0   1   0   0   0   0   -1  1/2 1/4 3/4
0   -1  0   1   0   0   0   0   -1  1/2 1/4 3/4
0   -1  0   1   0   0   0   0   -1  1/2 1/4 3/4
0   -1  0   1   0   0   0   0   -1  1/2 1/4 3/4
0   -1  0   1   0   0   0   0   1   1/4 1/2 3/4

【讨论】:

  • 我只需要行尾的分数 1/4 1/2 和 3/4
  • 所以改变顺序。而不是0 1 0 "${BASH_REMATCH[1]} 等等,将BASH_REMATCH 值移动到列表的末尾。输出中的顺序与 echo 命令中的顺序相同,所以很容易控制。
  • 此代码处理-x,但不仅仅是x。而且它不处理-y-z
  • 它还要求表达式按特定顺序yxz——我怀疑他的文件格式不是那么具体。
  • 是的,这适用于我为输入提供的行,但我想我应该为我的输入文件显示更多内容。感谢您的帮助,我会继续努力。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2019-12-31
  • 2012-10-18
  • 1970-01-01
  • 2022-12-15
  • 2022-07-12
  • 2021-03-13
  • 2017-07-10
相关资源
最近更新 更多