【问题标题】:Remove first two lines, last two lines and space from file and add quotes on each line and replace newline with commas in shell script从文件中删除前两行、最后两行和空格,并在每行添加引号,并在 shell 脚本中用逗号替换换行符
【发布时间】:2022-02-02 01:25:57
【问题描述】:

我必须输入.txt文件,该文件需要通过shell脚本格式化,条件如下

  1. 删除前两行并 最后两行
  2. 删除每个空格中的所有空格 行(每行有两个空格 开头和结尾一个空格)
  3. 每一行都应该在单行之内 引号('')
  4. 最后将换行符($)替换为 逗号。

(原创) input.txt

 sql
--------
  Abce
  Bca
  Efr
-------
Row (3)

所需的输出文件

output.txt

'Abce','Bca','Efr'

我尝试过使用以下命令

Sed -i 1,2d input.txt > input.txt
Sed "$(( $(wc -l <input.txt) -2+1)), $ d" Input.txt > input.txt
Sed ':a;N;$!ba;s/\n/, /g' input.txt > output.txt

但我得到空白的 output.txt

【问题讨论】:

  • This old answer of mine 可以作为起点。
  • 谢谢,stackoverflow 和 shell 脚本的新手
  • Sed 不是有效命令。请注意提供minimal reproducible example
  • 避免sed -i。 sed -i input.file &gt; input.file 是....嗯,很奇怪。 sed -i(如果确实有的话)的重点是“就地”修改文件而不产生任何输出。 &gt; file 的重点是将输出写入文件。 (但sed -i 不会产生输出!)。 sed -i 实际上并不直接修改文件,而是在副本上工作。但是 shell 会在 sed 开始之前截断文件(丢弃其中的所有数据),所以你最终什么都没有。

标签: bash shell awk sed scripting


【解决方案1】:

请您尝试以下方法:

mapfile -t ary < <(tail -n +3 input.txt | head -n -2 | sed -E "s/^[[:blank:]]*/'/; s/[[:blank:]]*$/'/")
(IFS=,; echo "${ary[*]}")
  • tail -n +3 输出第 3 行之后的行,包括第 3 行。
  • head -n -2 输出不包括最后 2 行的行。
  • sed -E "s/^[[:blank:]]*/'/" 删除前导空格和前置 单引号。
  • 同样,sed 命令"s/[[:blank:]]*$/'/" 删除尾随 空格并附加单引号。
  • 语法 mapfile 通过重定向。
  • mapfile -t ary 从标准输入读取行到数组中 名为 ary 的变量。
  • echo "${ary[*]}" 扩展为单个字符串,其内容为 数组ary 由IFS 的值分隔,刚刚分配 逗号。
  • IFS 的赋值和数组扩展用 要在子shell中执行的括号。这可以防止IFS 在当前进程中进行修改。

【讨论】:

    【解决方案2】:

    使用您展示的示例,请尝试关注awk 程序。用 GNU awk 编写和测试,应该适用于任何版本。

    awk -v s1="'" -v lines="$(wc -l < Input_file)" '
    BEGIN{ OFS="," }
    FNR==(lines-1) {
      print val
      exit
    }
    FNR>2{
      sub(/^[[:space:]]+/,"")
      val=(val?val OFS:"") (s1 $0 s1)
    }
    ' Input_file
    

    说明: 为上述代码添加详细说明,仅供说明之用。

    awk -v s1="'" -v lines="$(wc -l < Input_file)" '  ##Starting awk program, setting s1 variable to ' and creating lines which has total number of lines in it, using wc -l command on Input_file file.
    BEGIN{ OFS="," }                                ##Setting OFS to comma in BEGIN section of this program.
    FNR==(lines-1) {                                ##Checking condition if its 2nd last line of Input_file.
      print val                                     ##Then printing val here.
      exit                                          ##exiting from program from here.
    }
    FNR>2{                                          ##Checking condition if FNR is greater than 2 then do following.
      sub(/^[[:space:]]+/,"")                       ##Substituting initial spaces with NULL here.
      val=(val?val OFS:"") (s1 $0 s1)               ##Creating val which has ' current line ' in it and keep adding it in val.
    }
    ' Input_file                                    ##Mentioning Input_file name here.
    

    【讨论】:

    • 无法在 macOS 上运行,因为 wc
    • @Fravadona,它在 rhel-7 中对我来说工作得很好,我没有 Mac,所以无法测试它。
    • wc -l &lt; Input_file 的输出在 BSD 上以空格字符开头; echo $(wc -l &lt; Input_file) 会更便携
    • 只需引用 wc 的输出,它将按原样工作:lines="$(wc -l &lt; Input_file)",因为在您的脚本中使用lines 作为数字lines-1,将在标准的字符串->数字转换。
    【解决方案3】:

    如果您知道输入足够小以适合内存:

    $ awk '
        NR>4 { gsub(/^ *| *$/,"\047",p2); out=out sep p2; sep="," }
        { p2=p1; p1=$0 }
        END { print out }
    ' input.txt
    'Abce','Bca','Efr'
    

    否则:

    $ awk '
        NR>4 { gsub(/^ *| *$/,"\047",p2); printf "%s%s", sep, p2; sep="," }
        { p2=p1; p1=$0 }
        END { print "" }
    ' input.txt
    'Abce','Bca','Efr'
    

    任何一个脚本都可以在每个 Unix 机器上的任何 shell 中使用任何 awk 工作。

    【讨论】:

      【解决方案4】:

      这可能对你有用(GNU sed):

      sed -E '1,2d;$!H;$!d;x;s/^\s*(.*)\s*$/'\''\1'\''/mg;s/\n[^\n]*$//;y/\n/,/' file
      

      删除前两行。

      将每一行附加到保留空间,除了最后一行(这意味着倒数第二行仍然存在 - 见下文)。

      删除除最后一行之外的所有行。

      切换到保持空间。

      删除每行单词两侧的所有空格并用单引号将这些单词括起来。

      删除最后一行及其换行符。

      用逗号替换所有换行符。

      【讨论】:

        【解决方案5】:

        第一个sed -i 用一个空文件覆盖input.txt。你不能将输出写回你正在阅读的文件,sed -i 无论如何也不会产生任何输出。

        最小的解决方法是取出-i并将命令串在一起进入管道;但当然,sed 允许您将命令组合到一个脚本中。

        len=$(wc -l <input.txt)
        sed -e '1,2d' -e "$((len - 3))"',$d' \
            -e ':a' \
            -e 's/^  \(.*\) $/'"'\\1'/" \
            -e N -e '$!ba' -e 's/\n/, /g' input.txt >output.txt
        

        (未经测试;如果您的sed 不允许多个-e 选项,则需要重构以在命令之间使用带有分号或换行符的单个字符串。)

        这很难编写和调试,而且很脆弱,因为您必须将 shell 的引用功能与 sed 和这个特定脚本的要求结合起来,而且更本质上是因为 sed 是一个简洁和晦涩的语言。

        一个更清晰和可维护的解决方案是切换到 Awk,它允许您以更人性化的方式表达逻辑,并且避免不得不从 shell 中获取对算术和字符串格式化等简单任务的支持。

        awk 'FNR > 2 { sub(/^  /, ""); sub(/ $/, "");
            a[++i] = sprintf("\047%s\047,", $0); }
            END { for(j=1; j < i-1; ++j) printf "%s", a[j] }' input.txt >output.txt
        

        这实际上用逗号替换了所有换行符;也许您实际上想在最后一行打印换行符而不是逗号?

        awk 'FNR > 2 { sub(/^  /, ""); sub(/ $/, "");
            a[++i] = sprintf("%s\047%s\047", sep, $0); sep="," }
            END { for(j=1; j < i-1; ++j) printf "%s", a[j]; printf "\n" }' input.txt >output.txt
        

        如果输入文件非常大,您可能需要重构它以不将所有行都保留在内存中。数组a 收集格式化的输出,我们打印除END 块中的最后两个元素之外的所有元素。

        【讨论】:

          【解决方案6】:
          sed -E '
          /^-+$/,/^-+$/!d
          //d
          s/^[[:space:]]*|[[:space:]]*$/'\''/g
          ' input.txt |
          paste -sd ,
          
          • 这使用了一种不适用于所有 sed 实现的技巧,以打印两个模式(在本例中为破折号)之间的线条,排除这些模式。
          • 从好的方面来说,如果---- 模式位于不同的行号,它仍然可以工作。不利的一面是,如果该模式(仅包含破折号的线条)出现奇数次(即不是成对出现,而是包裹您想要的线条),则会中断。
          • 然后子行开始和结束(包括空格)用单引号。
          • 最后通过管道连接到paste,以逗号分隔新行,不包括尾随逗号。

          【讨论】:

            【解决方案7】:

            使用sed

            $ sed "1,2d; /-/,$ d; s/\s\+//;s/.*/'&'/" input_file | sed -z 's/\n/,/g;s/,$/\n/'
            'Abce','Bca','Efr'
            

            【讨论】:

              【解决方案8】:

              我将发布一个相当轻便的 sed 解决方案。

              sed '$d' input.txt | sed "\$d; 1,2d; s/^\s*\|\s*$/'/g" | paste -sd ',' > output.txt
              
              • $d 用第一个 sed 删除最后一行
              • \$d 删除最后一行。 $ 用反斜杠转义,因为我们在双引号内。
              • 1,2d 删除前两行。
              • s/^\s*\|\s*$/'/g 用单引号替换所有前导和尾随空格。
              • 使用paste 连接到一个以逗号分隔的字符串。

              如果我们知道相关行总是以两个空格开头,那么它甚至可以进一步简化。

              sed -n "s/\s*$/'/; s/^  /'/p" input.txt | paste -sd ',' > output.txt
              
              • -n 禁止打印行,除非被告知
              • s/\s*$/'/ 用单引号替换尾随空格
              • s/^ /'/p 替换两个前导空格并打印匹配的行
              • paste 连接

              然后是 awk 解决方案:

              awk -v i=1 -v q=\' 'FNR>2 {
                  gsub(/^[[:space:]]*|[[:space:]]*$/, q)
                  a[i++]=$0
              } END {
                  for(i=1; i<=length(a)-3; i++)
                      printf "%s,", a[i]
                  print a[i++]
              }' input.txt > output.txt
              
              • -v i=1 创建一个从 1 开始的 awk 变量
              • -v q=\' 为单引号字符创建一个 awk 变量
              • FNR&gt;2 { ... 告诉它只处理第 3+ 行
              • gsub(/^[[:space:]]*|[[:space:]]*$/, q) 用单引号替换前导和尾随空格
              • a[i++]=$0向数组添加行
              • END { ... 到达文件末尾后处理其余部分
              • for(i=1; i&lt;=length(a)-3; i++) 取数组的长度减去三——代表最后三行
              • printf "%s,", a[i] 打印除最后三个条目之外的所有条目,以逗号分隔
              • print a[i++] 打印下一个条目并完成脚本(跳过最后两个条目)

              【讨论】:

                【解决方案9】:

                不是单行但有效

                sed "s/^ */\'/;s/\$/\',/;1,2d;N;\$!P;\$!D;\$d"  | sed ' H;1h;$!d;x;s/\n//g;s/,$//'
                

                解释:

                s/^ */\'/;s/\$/\',/ ---> 添加单引号和逗号

                N;$!P;$!D;$d ---> 删除最后两行

                H;1h;$!d;x;s/\n//g;s/,$//' ---> 加载整个文件并合并所有行并删除最后一个逗号

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-02-14
                  • 2023-03-10
                  • 2013-08-05
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多