【问题标题】:Append to last (or n-th) line of function (using awk/sed)附加到函数的最后(或第 n 个)行(使用 awk/sed)
【发布时间】:2021-05-18 19:55:42
【问题描述】:

最好使用awk、sed或类似的,我如何实现append_last_line,例如。在函数主体的最后行附加一些内容,可以是给定文件中的任何位置?

# file.sh
foo ()
{
  echo "bar"
}

$ append_last_line 'foo' 'echo "baz"' file.sh

# file.sh
foo ()
{
  echo "bar"
  echo "baz"
}

第n行呢?

# file.sh
foo ()
{
  echo "bar"
  echo "baz"
}

$ append_nth_line 1 'foo' 'echo "qux"' file.sh

# file.sh
foo ()
{
  echo "baz"
  echo "qux"
  echo "bar"
}

【问题讨论】:

  • What about the n-th line 是什么?功能?文件?别的东西?确保您的示例输入/输出满足您的所有要求。
  • @eheu 简单的解决方案(对于复杂的问题可能还不够,也就是说,您可能需要一个真正的 shell 解析器来识别函数的开始位置):打开输入文件,搜索 ^function (),搜索 @ 987654330@,将除} 之外的所有内容写到临时文件中,写下你的行,写},将所有内容复制到文件末尾到新文件,关闭输入文件,将临时文件重命名为新文件。跨度>
  • @eheu 用于函数的第 n 行:搜索 ^{,开始计算行数(并检查您在第 n 行之前没有到达 ^}),然后写下您的行和输入文件的其余部分。
  • @EdMorton 我已将我的问题编辑为更易于理解。
  • @rturrado 这可行!不过,我想知道是否有人可以想到使用一些 sed 或 awk 代码的解决方案。您是否考虑过 vi 来实施您的解决方案?

标签: bash awk sed sh


【解决方案1】:

让你开始的东西:

awk -v name=foo -v num=2 -v what='echo "qux"' '
   # Print the line and read next line and handle getline error
   function print_getline() {
        print;
        if (getline <= 0) exit 1;
   }
   $0 ~ name "[[:space:]]*()"{                     # match the function followed by ()
          while ($0 !~ "{") { print_getline(); }   # wait for {
          while (num--) { print_getline(); }       # omit num count lines
          print what;                              # add the line you want to add
   }
   { print }   # print all other lines
' <<EOF
# file.sh
foo ()
{
  echo "bar"
  echo "baz"
}
EOF

将输出:

# file.sh
foo ()
{
  echo "bar"
echo "qux"
  echo "baz"
}

网上有很多关于awk的好介绍。 awk 是一门很棒的语言。但是,如果您打算编写一个完整的 shell 语言解析器来处理所有极端情况(即echo "foo () {" 不是函数定义...),请考虑使用更强大的语言,例如 perl 或 python。

【讨论】:

    【解决方案2】:

    显然,您为此编写的任何脚本都将是脆弱的,因为您需要一个用于编写目标文件的语言的解析器才能稳健地完成这项工作,但您提供的示例可以满足您的需求:

    $ cat tst.sh
    #!/usr/bin/env bash
    
    (( $# > 3 )) && { tgtLineNr=$1; shift; }
    tgtFunc=$1
    newText=$2
    shift 2
    
    awk -v tgtLineNr="$tgtLineNr" -v tgtFunc="$tgtFunc" -v newText="$newText" '
    
        state == "gotFuncBeg" {
            if ( /^}/ ) { state = "gotFuncEnd" }
            if ( (lineNr++ == tgtLineNr) || ((state == "gotFuncEnd") && !tgtLineNr) ) {
                sub(/[^[:space:]].*/,"",indent)
                print indent newText
            }
            if ( NF ) { indent = $0 }
        }
    
        (state == "gotFuncName") && /^{/ {
            state = "gotFuncBeg"
            lineNr = 0
        }
    
        /^[[:alpha:]_][[:alnum:]_]*[[:space:]]*\(/ {
            thisFunc = $0
            sub(/[[:space:]]*\(.*/,"",thisFunc)
            state = (thisFunc == tgtFunc ? "gotFuncName" : "")
        }
    
        { print }
    ' "$@"
    

    $ cat file
    # file.sh
    foo ()
    {
      echo "bar"
      echo "baz"
    }
    

    $ ./tst.sh 'foo' 'echo "qux"' file
    # file.sh
    foo ()
    {
      echo "bar"
      echo "baz"
      echo "qux"
    }
    

    $ ./tst.sh 1 'foo' 'echo "qux"' file
    # file.sh
    foo ()
    {
      echo "bar"
      echo "qux"
      echo "baz"
    }
    

    【讨论】:

      【解决方案3】:

      感谢您提供的精彩脚本,伙计们!我一定会深入研究它们。

      我最终根据@rturrado 的comment 编写了这个脚本

      $ cat append-nth-line.sh
      #!/bin/bash
      IFS=''
      
      function_name=$1
      nth=$2
      append=$3
      out=$4
      
      declare function_start_line
      declare function_end_line
      
      search="on"
      i=1
      
      while read -r line; do
        echo $line >>tmp1
        if [[ $(echo $line | xargs) =~ ^($function_name[[:space:]]*?\(\)) ]]; then
          function_start_line=$i
        fi
        if [ $function_start_line ]; then
          if [[ $(echo $line | xargs) =~ ^} && $search = "on" ]]; then
            function_end_line=$i
            search="off"
          fi
        fi
        i=$(($i + 1))
      done
      
      if [[ "$nth" =~ ^- ]]; then
        find=$(($function_end_line + $nth))
      else
        find=$(($function_start_line + $nth))
      fi
      
      i=1
      
      cat tmp1 |
        while read -r line; do
          echo $line >>tmp2
          if [[ $i = $find ]]; then
            indent=$(echo $line | grep -o ^'[[:space:]]*')
            if [[ ! "$nth" =~ ^- && $(echo $line | xargs) =~ \{|then|else|do ]]; then
              indent="$indent  "
            fi
            echo "$indent$append" >>tmp2
          fi
          i=$(($i + 1))
        done
      
      rm tmp1
      chmod +x tmp2
      mv tmp2 $out
      
      
      $ cat file.sh
      foo()
      {
        echo "bar"
      }
      
      
      $ append_nth_line.sh "foo" -1 'echo "baz"' file.sh  < file.sh 
      $ cat file.sh
      foo()
      {
        echo "bar"
        echo "baz"
      }
      
      
      $ append_nth_line.sh "foo" 1 'echo "qux"' file.sh  < file.sh 
      $ cat file.sh
      foo()
      {
        echo "qux"
        echo "bar"
        echo "baz"
      }
      
      

      我还有一些边缘情况需要解决,但这个或你的脚本会做。

      欢迎反馈。

      【讨论】:

      • 请阅读why-is-using-a-shell-loop-to-process-text-considered-bad-practice 以了解不这样做的一些原因,将脚本复制/粘贴到shellcheck.net 以解决其他问题,而且几乎可以肯定还有更多问题未涵盖其中。 shell 是一个环境,可以从中调用工具(并操作文件/进程),并使用一种语言来对这些调用进行排序,它不是操作文本的工具。发明 shell 的人也为这项任务发明了 awk。
      • @EdMorton 感谢分享!我对此一无所知。实际上,到目前为止,我很高兴在命令行和小脚本中使用 for 循环和 while read 循环。我一定要学awk!
      • 不客气。如果您发现自己正在考虑使用 for 循环,那么另一本好书:mywiki.wooledge.org/BashFAQ/001
      【解决方案4】:

      …sed 或类似的

      使用

      set -- filename funcname -1 '  echo "quux"'
      printf '%s\n' '/^'"$2"'[ (]/;#' '/^}/;#' "$3"a "$4" . w q | ed -rs "$1"
      

      查找funcname,然后查找结尾的},将文本附加到 } (-1) 之前的一行,最后保存编辑的文件。

      最后一行使用 -1 调用,倒数第三行使用 -3 ...

      【讨论】:

        【解决方案5】:

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

        # append_last_line 'foo' 'echo "baz"' file.sh
        append_last_line (){ sed '/^'$1'\s*()/{:a;h;n;/^}$/!ba;x;s/\S.*/'"$2"'/p;x}' "$3"; }
        
        # append_nth_line 2 'foo' 'echo "baz"' file.sh N.B. n must be natural number > 0
        append_nth_line (){ sed -E '/^'$2'\s*\(\)/{n;n;:a;N;/^}/M!ba;s/(((\s*)[^\n]*(\n)){'$1'})/&\3'"$3"'\4/}' "$4"; }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-08-27
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-11-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多