【问题标题】:Parse ps' "etime" output and convert it into seconds解析 ps 的“etime”输出并将其转换为秒
【发布时间】:2013-01-17 03:04:37
【问题描述】:

这些是ps h -eo etime 的可能输出格式

21-18:26:30
   15:28:37
      48:14
      00:01

如何将它们解析为秒?

  • 请假设天数部分至少为 3 位数,因为我不知道它可以有多长。
  • 输出将是egreped 到仅一行,因此无需循环。

【问题讨论】:

  • 这不会回答你的问题,但为什么不直接使用etimes 呢?它以秒为单位为您提供相同的值。
  • 什么是etimes?一个程序? ps的格式选项?
  • 一个ps 格式选项,与etime 相同,除了输出以秒为单位而不是[[DD-]hh:]mm:ss
  • @Hasturkun RedHat 版本的ps 没有etimes 格式说明符。 ps h -eo etime,etimes ERROR: Unknown user-defined format specifier "etimes".
  • CentOS 也是如此(因为它是从 RHEL 派生的)

标签: regex linux bash parsing type-conversion


【解决方案1】:

尝试将我的解决方案与 sed+awk 一起使用:

ps --pid $YOUR_PID -o etime= | sed 's/:\|-/ /g;' |\ 
awk '{print $4" "$3" "$2" "$1}' |\
awk '{print $1+$2*60+$3*3600+$4*86400}'

它用 sed 分割字符串,然后将数字倒转(“DD hh mm ss”->“ss mm hh DD”)并用 awk 计算它们。

您还可以使用 sed 并从输入字符串中删除所有非数字字符:

sed 's/[^0-9]/ /g;' | awk '{print $4" "$3" "$2" "$1}' | awk '{print $1+$2*60+$3*3600+$4*86400}'

【讨论】:

  • 与其他替代方案相比,它很清晰但效率低下。如果我只需要执行一次,我会使用它。
【解决方案2】:

Python 版本:

ex=[
    '21-18:26:30',
    '06-00:15:30',
    '15:28:37',
    '48:14',
    '00:01'
    ]

def etime_to_secs(e):
    t=e.replace('-',':').split(':')
    t=[0]*(4-len(t))+[int(i) for i in t]
    return t[0]*86400+t[1]*3600+t[2]*60+t[3]

for e in ex:
    print('{:11s}: {:d}'.format(e, etime_to_secs(e)))

【讨论】:

    【解决方案3】:

    作为函数的另一个 bash 选项;使用 tac 和 bc 进行数学运算。

    function etime2sec () {
       # 21-18:26:30
       #    15:28:37
       #       48:14
       #       00:01
    etimein=$1
    hassec=no ; hasmin=no ; hashr=no ; hasday=no
    newline=`echo "${etimein}" | tr ':' '-' | tr '-' ' ' | tac -s " " | tr '\n' ' '`
    for thispiece in $(echo "${etimein}" | tr ':' '-' | tr '-' ' ' | tac -s " " | tr '\n' ' ') ; do
      if [[ $hassec = no ]] ; then
        totsec=$thispiece
        hassec=yes
      elif [[ $hasmin = no ]] ; then
        totsec=`echo "$totsec + ($thispiece * 60)" | bc`
        hasmin=yes
      elif [[ $hashr = no ]] ; then
        totsec=`echo "$totsec + ($thispiece * 3600)" | bc`
        hashr=yes
      elif [[ $hasday = no ]] ; then
        totsec=`echo "$totsec + ($thispiece * 86400)" | bc`
        hashr=yes
      fi
    done
    echo $totsec
    }
    

    【讨论】:

      【解决方案4】:

      这是我的 Perl 单行代码:

      ps -eo pid,comm,etime | perl -ane '@t=reverse split(/[:-]/,$F[2]); $s=$t[0]+$t[1]*60+$t[2]*3600+$t[3]*86400; print "$F[0]\t$F[1]\t$F[2]\t$s\n"'
      

      未定义的值呈现为零,因此它们不会影响秒数的总和。

      【讨论】:

      • 这段代码很有用,漂亮,简单,优雅,可读! +10。
      • 这太棒了!当我无法访问 etimes 时救了我
      【解决方案5】:

      适用于 AIX 7.1:

      ps -eo etime,pid,comm | awk '{if (NR==1) {print "-1 ",$0} else {str=$1; sub(/-/, ":", str="0:0:"str); n=split(str,f,":"); print 86400*f[n-3]+3600*f[n-2]+60*f[n-1]+f[n]," ",$0}}' | sort -k1n
      

      【讨论】:

        【解决方案6】:

        我只需要添加我的版本,很大程度上基于@andor 的优雅 perl one-liner(漂亮的 perl 代码!)

        • 时间:自开始以来的总 CPU 时间(?或它的一些计算,如果 cpu 使用率下降,它会衰减?我不确定....虽然高数字表示 cpu 密集型进程)
        • etime:自进程启动以来经过的总时间
        • tail 的 2 种方式:在 linux 上:tail +2 不起作用。在 solaris 上,tail -n +2 不起作用。所以我会尝试两者来确定。

        这里是计算时间的方法,以及如何根据进程的平均 CPU 使用率对进程进行排序

        ps -eo pid,comm,etime,time | { tail +2 2>/dev/null || tail -n +2 ;} | perl -ane '
            @e=reverse split(/[:-]/,$F[2]); $se=$e[0]+$e[1]*60+$e[2]*3600+$e[3]*86400;
            @t=reverse split(/[:-]/,$F[3]); $st=$t[0]+$t[1]*60+$t[2]*3600+$t[4]*86400; 
            if ( $se == 0 ) { $pct=0 ; } else { $pct=$st/$se ;};
            printf "%s\t%s\t%s(%sseconds)\t%s(%sseconds)\t%.4f%%\n",$F[0],$F[1],$F[2],$se,$F[3],$st,$pct*100;
           '  | sort -k5,5n
        

        【讨论】:

          【解决方案7】:

          我想我可能错过了这里的重点,但最简单的方法是:

          ps h -eo etimes
          

          注意 etime 末尾的“s”。

          【讨论】:

          • 在 AIX 和 solaris 上:# ps -eo etimes > a ; ps -eo etime > b ; diff a b:两者都提供相同的输出...
          • centos 6,ps不支持etimes
          【解决方案8】:

          使用 awk:

          #!/usr/bin/awk -f  
          BEGIN { FS = ":" }
          {
            if (NF == 2) {
              print $1*60 + $2
            } else if (NF == 3) {
              split($1, a, "-");
              if (a[2] != "" ) {
                print ((a[1]*24+a[2])*60 + $2) * 60 + $3;
              } else {
                print ($1*60 + $2) * 60 + $3;
              }
            }
          }
          

          运行:

          awk -f script.awk datafile
          

          输出:

          1880790
          55717
          2894
          1
          

          最后,如果你想通过管道传输到解析器,你可以这样做:

          ps h -eo etime | ./script.awk
          

          【讨论】:

          • 我会小心使用上面的例子。虽然它看起来通过了提供的测试标准,但它为指定日期且小时为零的值提供了不正确的答案:06-00:15:30 返回答案 22530,这显然是不正确的(答案是 519330 )。
          • 呃。你的答案没有错(赞成)。但是,呃,说真的,Unix?认真的吗?
          • @markeissler 是的,你是对的。更改了条件,使其在零小时内工作
          • 这对我来说开箱即用,谢谢。我需要它能够检查我的进程已经运行的秒数,看看它是否大于我定义的阈值。
          【解决方案9】:
          [[ $(ps -o etime= REPLACE_ME_WITH_PID) =~ ((.*)-)?((.*):)?(.*):(.*) ]]
          printf "%d\n" $((10#${BASH_REMATCH[2]} * 60 * 60 * 24 + 10#${BASH_REMATCH[4]} * 60 * 60 + 10#${BASH_REMATCH[5]} * 60 + 10#${BASH_REMATCH[6]}))
          

          BASH。 BASH_REMATCH 变量需要 BASH 2+ (?)。正则表达式匹配任何输入并将匹配的字符串放入数组 BASH_REMATCH,其中的部分用于计算秒数。

          【讨论】:

            【解决方案10】:

            我已经实现了一个 100% 的 bash 解决方案,如下所示:

            #!/usr/bin/env bash
            
            etime_to_seconds() {
              local time_string="$1"
              local time_string_array=()
              local time_seconds=0
              local return_status=0
            
              [[ -z "${time_string}" ]] && return 255
            
              # etime string returned by ps(1) consists one of three formats:
              #         31:24 (less than 1 hour)
              #      23:22:38 (less than 1 day)
              #   01-00:54:47 (more than 1 day)
              #
            
              # convert days component into just another element
              time_string="${time_string//-/:}"
            
              # split time_string into components separated by ':'
              time_string_array=( ${time_string//:/ } )
            
              # parse the array in reverse (smallest unit to largest)
              local _elem=""
              local _indx=1
              for(( i=${#time_string_array[@]}; i>0; i-- )); do
                _elem="${time_string_array[$i-1]}"
                # convert to base 10
                _elem=$(( 10#${_elem} ))
                case ${_indx} in
                  1 )
                    (( time_seconds+=${_elem} ))
                    ;;
                  2 )
                    (( time_seconds+=${_elem}*60 ))
                    ;;
                  3 )
                    (( time_seconds+=${_elem}*3600 ))
                    ;;
                  4 )
                    (( time_seconds+=${_elem}*86400 ))
                    ;;
                esac
                (( _indx++ ))
              done
              unset _indx
              unset _elem
            
              echo -n "$time_seconds"; return $return_status
            }
            
            main() {
              local time_string_array=( "31:24" "23:22:38" "06-00:15:30" "09:10" )
            
              for timeStr in "${time_string_array[@]}"; do
            
                  local _secs="$(etime_to_seconds "$timeStr")"
                  echo "           timeStr: "$timeStr""
                  echo "  etime_to_seconds: ${_secs}"
              done
            
            }
            
            main
            

            【讨论】:

            • 在第 33 行解析 08 和 09 时似乎出现了错误(( time_seconds+=${_elem} ))。你会得到:“09:值对基数太大(错误标记为“09”)”或“08:值对基数太大(错误标记为“08”)”。这样做的原因是,由于数字以 0 开头,它们被解释为十六进制表示法。如果您使用 (( time_seconds+=$((10#$_elem)) )) 代替,错误就会消失,因为它表示您正在使用的基数(在本例中为基数 10)。
            • 谢谢@aemus。我更新了答案以更好地整合您的修复。
            【解决方案11】:

            另一种 bash 解决方案,适用于任意数量的字段:

            ps -p $pid -oetime= | tr '-' ':' | awk -F: '{ total=0; m=1; } { for (i=0; i < NF; i++) {total += $(NF-i)*m; m *= i >= 2 ? 24 : 60 }} {print total}'

            解释:

            1. - 替换为: 使字符串变为1:2:3:4 而不是 '1-2:3:4',将总数设为 0,乘​​数设为 1
            2. 除以:,从最后一个字段(秒)开始,乘以 m = 1,加到总秒数,m 变为 60(一分钟的秒数)
            3. 添加分钟字段乘以 60,m 变为 3600
            4. 加小时 * 3600
            5. 加天 * 3600 * 24

            【讨论】:

            • 在所有解决方案中,我相信这是最优雅的一个,就可维护性、可移植性和代码的简单性而言。
            • 非常有用且有效的代码。我只是想知道 FOR 循环中的 NF 变量是什么。谢谢!
            • NF = 字段数(在当前正在处理的行中)
            【解决方案12】:

            Ruby 版本:

            def psETime2Seconds(etime)
              running_secs = 0
              if etime.match(/^(([\d]+)-)?(([\d]+):)?([\d]+):([\d]+)$/)
                running_secs += $2.to_i * 86400 # days
                running_secs += $4.to_i * 3600  # hours
                running_secs += $5.to_i * 60    # minutes
                running_secs += $6.to_i         # seconds
              end
              return running_secs
            end
            

            【讨论】:

              【解决方案13】:
              #!/bin/bash
              echo $1 | sed 's/-/:/g' |  awk -F $':' -f <(cat - <<-'EOF'
                {
                  if (NF == 1) {
                      print $1
                  }
                  if (NF == 2) {
                      print $1*60 + $2
                  }
                  if (NF == 3) {
                      print $1*60*60 + $2*60 + $3;
                  }
                  if (NF == 4) {
                      print $1*24*60*60 + $2*60*60 + $3*60 + $4;
                  }
                  if (NF > 4 ) {
                      print "Cannot convert datatime to seconds"
                      exit 2
                  }
                }
              EOF
              ) < /dev/stdin
              

              然后运行使用:

              ps -eo etime | ./script.sh 
              

              【讨论】:

              • 太棒了!这正是我所需要的。谢谢。
              【解决方案14】:

              这是一个 PHP 替代方案,可读且经过全面单元测试:

              //Convert the etime string $s (as returned by the `ps` command) into seconds
              function parse_etime($s) {
                  $m = array();
                  preg_match("/^(([\d]+)-)?(([\d]+):)?([\d]+):([\d]+)$/", trim($s), $m); //Man page for `ps` says that the format for etime is [[dd-]hh:]mm:ss
                  return
                      $m[2]*86400+    //Days
                      $m[4]*3600+     //Hours
                      $m[5]*60+       //Minutes
                      $m[6];          //Seconds
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2011-03-29
                • 1970-01-01
                • 2019-05-24
                • 1970-01-01
                • 2010-11-05
                • 2019-05-25
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多