首页 > 解决方案 > 管道后如何在子shell内使用while循环

问题描述

我已经寻找并寻找解决方案,但我找不到任何直接解决我的问题的方法。我正在尝试将某些作者在 git repo 中添加和删除的行加起来。我正在使用 git log piped to sed piped to awk,我现在正尝试通过管道连接到 subshel​​l 以将数字相加。问题是管道输入在子shell中没有得到正确解释,我不知道为什么。我怀疑它在 while 循环中,因为 subshel​​l 语法的性质及其对分号的挑剔。

我已经在子shell 内移动了代码,添加和删除了分号,使用反斜杠进行行分隔以查看是否是问题所在,但都没有奏效。我不精通shell,所以对于更有经验的人来说,这可能是一个明显的问题。"$author" 只是命令行中的第 n 个位置参数。

for author; do
        echo "Listing file and line changes for $author"
        git log --shortstat --author="$author" ${date:+--since="$date"} \
        | sed -n -e '/files\? changed/s/, /\n/gp' \
        | awk '
            $3=="changed"       {changed+=$1}
            $2=="deletions(-)"  {deletions+=$1}
            $2=="insertions(+)" {insertions+=$1}
            END{
                print "files changed:", changed,
                    " lines removed:", deletions,
                    " lines added:", insertions,
                    " net change:", insertions-deletions
            }'
done | {
      total_changed=0
      total_added=0
      total_removed=0
      while read changed insertions deletions; do
        let total_changed+=changed
        let total_added+=insertions
        let total_removed+=deletions
      done
      echo "totals:"
      echo "files changed: $total_changed"
      echo "lines added: $total_added"
      echo "lines removed: $total_removed" ;
    }

最后一部分应该输出总数,但它们输出的是 0。我还遇到了一些奇怪的语法错误。这是输出(输入是“Benjamin Hills”):

    /home/bhills/./git-log-lines-removed.sh: line 65: let: and line changes for Benjamin Hills: syntax error in expression (error token is "line changes for Benjamin Hills")
    /home/bhills/./git-log-lines-removed.sh: line 64: let: changed:: syntax error in expression (error token is ":")
    /home/bhills/./git-log-lines-removed.sh: line 65: let: 61  lines removed: 1345  lines added: 246  net change: -1099: syntax error in expression (error token is "lines removed: 1345  lines added: 246  net change: -1099")
    totals:
    files changed: 0
    lines added: 0
    lines removed: 0

标签: bashshellawksed

解决方案


您的代码试图生成两次人类可读的输出:一次在 awk 中,另一次在 bash 中。由于输出awk生成是输入到bash,这意味着您正在尝试通过管道传输一种用于人类的输出格式,并将其读取为就好像它是一种用于机器的输入格式一样;显然,它不是。

完全没有理由采用这种方法:在同一个过程中生成所有人类可读的输出。

#!/usr/bin/env bash
#              ^^^^- NOT /bin/sh

total_changed=0
total_deletions=0
total_insertions=0

for author; do
  changed=0; deletions=0; insertions=0

  # Loop over output from "git log" for a single author
  while read added deleted _; do
    (( ++changed )) # each line is a file changed
    { [[ $added = - ]] || [[ $deleted = - ]]; } && continue # skip binary files
    (( insertions += added ))
    (( deletions += deleted ))
  done < <(git log --numstat --format='' --author="$author" ${date:+--since="$date"})

  # Print results from that author
  printf '%s\n' "For author: $author" \
                "  Files changed: $changed" \
                "  Deletions: $deletions" \
                "  Insertions: $insertions"

  # Add to totals
  (( total_changed+=changed ))
  (( total_deletions+=deletions ))
  (( total_insertions+=insertions ))
done

# Print those totals
printf '%s\n' "Totals:" \
              "  Files changed: $total_changed" \
              "  Deletions: $total_deletions" \
              "  Insertions: $total_insertions"

推荐阅读