pgrep -P,但对于孙子代而言,不只是子代

时间:2018-09-26 02:00:30

标签: bash shell recursion grep

我正在使用:

pgrep -P $$

获取$$的孩子pid。但是我实际上也想要一个孙辈和曾孙辈的名单。

我该怎么做?使用常规的编程语言,例如,我们可以使用递归来实现,但是使用bash?也许使用bash函数?

6 个答案:

答案 0 :(得分:3)

我已经发布了attempted solution。它简短有效,并且似乎符合OP的问题,因此我将其保留不变。但是,它存在一些性能和可移植性问题,这意味着它不是一个很好的通用解决方案。这段代码试图解决问题:

top_pid=$1

# Make a list of all process pids and their parent pids
ps_output=$(ps -e -o pid= -o ppid=)

# Populate a sparse array mapping pids to (string) lists of child pids
children_of=()
while read -r pid ppid ; do
    [[ -n $pid && pid -ne ppid ]] && children_of[ppid]+=" $pid"
done <<< "$ps_output"

# Add children to the list of pids until all descendants are found
pids=( "$top_pid" )
unproc_idx=0    # Index of first process whose children have not been added
while (( ${#pids[@]} > unproc_idx )) ; do
    pid=${pids[unproc_idx++]}       # Get first unprocessed, and advance
    pids+=( ${children_of[pid]-} )  # Add child pids (ignore ShellCheck)
done

# Do something with the list of pids (here, just print them)
printf '%s\n' "${pids[@]}"

保留了使用广度优先搜索来构建树的基本方法,但是有关流程的基本信息是通过运行一次ps(符合POSIX的)来获得的。 pgrep不再使用,因为它不在POSIX中,并且可以运行多次。另外,从队列中删除项目(复制除其中一个元素之外的所有元素)的效率非常低下的方式已由操纵索引变量代替。

在我的老式Linux系统上使用pid 0在大约400个进程上运行时,平均(实际)运行时间为0.050s。

我仅在Linux上对其进行了测试,但是它仅使用Bash 3功能和ps的POSIX兼容功能,因此它也应在其他系统上工作。

答案 1 :(得分:2)

仅使用bash内置函数(甚至不使用pspgrep!)

#!/usr/bin/env bash

collect_children() {
  # format of /proc/[pid]/stat file; group 1 is PID, group 2 is its parent
  stat_re='^([[:digit:]]+) [(].*[)] [[:alpha:]] ([[:digit:]]+) '

  # read process tree into a bash array
  declare -g children=( )              # map each PID to a string listing its children
  for f in /proc/[[:digit:]]*/stat; do # forcing initial digit skips /proc/net/stat
    read -r line <"$f" && [[ $line =~ $stat_re ]] || continue
    children[${BASH_REMATCH[2]}]+="${BASH_REMATCH[1]} "
  done
}

# run a fresh collection, then walk the tree
all_children_of() { collect_children; _all_children_of "$@"; }

_all_children_of() {
  local -a immediate_children
  local child
  read -r -a immediate_children <<<"${children[$1]}"
  for child in "${immediate_children[@]}"; do
    echo "$child"
    _all_children_of "$child"
  done
}

all_children_of "$@"

在我的本地系统上,time all_children_of 1 >/dev/null(在已经运行的shell中调用该功能)的时钟约为0.018s-通常在collect_children阶段为0.013s(一次)读取进程树的操作),并为_all_children_of的初始调用触发的树的递归遍历设置0.05s。

以前的时间仅测试步行所需的时间,而忽略了扫描所需的时间。

答案 2 :(得分:1)

下面的代码将打印当前进程及其所有后代的PID。它使用Bash数组作为队列来实现进程树的breadth-first search

unprocessed_pids=( $$ )
while (( ${#unprocessed_pids[@]} > 0 )) ; do
    pid=${unprocessed_pids[0]}                      # Get first elem.
    echo "$pid"
    unprocessed_pids=( "${unprocessed_pids[@]:1}" ) # Remove first elem.
    unprocessed_pids+=( $(pgrep -P $pid) )          # Add child pids
done

答案 3 :(得分:0)

大概一个简单的循环就可以做到:

# set a value for pid here
printf 'Children of %s:\n' $pid
for child in $(pgrep -P $pid); do
    printf 'Children of %s:\n' $child
    pgrep -P $child
done

答案 4 :(得分:0)

如果pgrep不能满足您的要求,您可以随时直接使用ps。选项将在某种程度上取决于平台。

ps -o ppid,pid |
awk -v pid=$$ 'BEGIN { parent[pid] = 1 }  # collect interesting parents
    { child[$2] = $1 }  # collect parents of all processes
    $1 == pid { parent[$2] = 1 }
    END { for (p in child)
        if (parent[child[p]])
          print p }'

变量名不是正交的-parent收集pid或其子项之一作为键的过程,即“有趣的”父项,而child包含父项每个流程,以流程为键,父流程为值。

答案 5 :(得分:0)

我最终使用node.js和bash做到了这一点:

// type that simply serves as a data container (replacing the ValueTuple)
private class Data : IEquatable<Data>
{
    private const int maxNumberOfRandom = 3;

    public readonly int[] Values1 = new int[maxNumberOfRandom];
    public readonly int[] Values2 = new int[maxNumberOfRandom];
    public bool IsNull { get; set; }

    public bool Equals(Data other)
    {
        return CompareArrays(Values1, other.Values1) && CompareArrays(Values2, other.Values2);
    }

    private static bool CompareArrays(int[] values, int[] otherValues)
    {
        for (var i = 0; i < maxNumberOfRandom; i++)
        {
            if (values[i] != otherValues[i])
            {
                return false;
            }
        }
        return true;
    }

    public override int GetHashCode()
    {
        unchecked
        {
            var hashCode = Values1.GetHashCode();
            hashCode = (hashCode * 397) ^ Values2.GetHashCode();
            return hashCode;
        }
    }
}

static void Main(string[] args)
{
    const int count = 20000;

    var list = new List<Data>(count);
    // initialization loop to provision the required memory on the heap
    for (int i = 0; i < count; i++)
    {
        list.Add(new Data());
    }

    while (true)
    {
        var rng = new Random(1);

        Parallel.For(0, 20000, i =>
        {
            // Random isn't thread-safe: https://docs.microsoft.com/en-us/dotnet/api/system.random?view=netframework-4.7.2#the-systemrandom-class-and-thread-safety
            int r;
            lock (rng)
            {
                r = rng.Next(0, 3);
            }

            if (r == 0)
            {
                // we can do index-based access here so no need for locking
                list[i].IsNull = true;
            }
            else
            {
                // we can do index-based access here so no need for locking
                var data = list[i];
                data.IsNull = false;

                int j;
                for (j = 0; j < r; j++)
                {
                    data.Values1[j] = j;
                    data.Values2[j] = j * j;
                }
                Array.Clear(data.Values1, j, data.Values1.Length - j);
                Array.Clear(data.Values2, j, data.Values2.Length - j);
            }
        });

        var sw = Stopwatch.StartNew();
        var results = list.ToList().AsParallel().Where(x => !x.IsNull).Distinct().ToList();
        var time = sw.ElapsedMilliseconds;

        Console.WriteLine($"CB count: {list.Count:00000}, result count: {results.Count:00}, time: {time:000}");
    }
}