Digging with ps
looking for a process isn’t that pleasure.
Of course we can grep, but it’s still hard to read values without headings, and determine launch hierarchy.
On some linux distros there is the pstree
command, I tried to do something similar for this pssearch
.
This utility is also useful to determine which process is using a file or a folder, e.g. preventing its deletion.
Syntax
pssearch
requires one argument:
pssearch path-to-file-or-folder
pssearch pid-number
pssearch case-insensitive-part-of-command
Please note:
- When the argument is a path to an existing file or folder then will be reported the process (if any) that is using the file or the folder.
- When the argument only contains numbers it’s assumed to be a PID number.
case-insensitive-part-of-command
is used inside a regex case insensitive match, it can actually be a simple regex - e.g.: “chrome.+content
”
Therefore if you want to use verbatim for special characters (like .+*?()[]$\
) prexif them with double backslash - e.g.: + => \\+, \ => \\\\
pssearch
accepts one and only one argument.
Should such argument contain whitespaces, wrap it with quotes or quote whitespaces with \
.
E.g.: pssearch "google chrome"
, pssearch google\ chrome
Output
% pssearch pssearch
Path: /Users/marco/Documents/dev/pssearch/pssearch
`--> In use by PID 21877
[PID:1] [USER:root] [TIME:12:52.48] [STAT:Ss] /sbin/launchd
`--> [PID:21877] [USER:marco] [TIME:2:28.12] [STAT:S] /Applications/BBEdit.app/Contents/MacOS/BBEdit
% pssearch chrome
[PID:1] [USER:root] [TIME:0:34.36] [STAT:Ss] /sbin/launchd
`--> [PID:515] [USER:marco] [TIME:0:23.62] [STAT:S] /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
[PID:1] [USER:root] [TIME:0:34.36] [STAT:Ss] /sbin/launchd
`--> [PID:515] [USER:marco] [TIME:0:23.62] [STAT:S] /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
`--> [PID:758] [USER:marco] [TIME:0:04.92] [STAT:S] /Applications/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Versions/117.0.5938.149/Helpers/Google Chrome Helper (Renderer).app/Contents/MacOS/Google Chrome Helper (Renderer) --type=renderer --disable-nacl --origin-trial-disabled-features=WebGPU --lang=en-GB --num-raster-threads=4 --enable-zero-copy --enable-gpu-memory-buffer-compositor-resources --enable-main-frame-before-activation --renderer-client-id=12 --time-ticks-at-unix-epoch=-1696577818407481 --launch-time-ticks=74899392 --shared-files --field-trial-handle=1718379636,r,658519363837203859,834477175066415890,262144 --seatbelt-client=89
[PID:1] [USER:root] [TIME:0:34.36] [STAT:Ss] /sbin/launchd
`--> [PID:515] [USER:marco] [TIME:0:23.62] [STAT:S] /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
`--> [PID:6100] [USER:marco] [TIME:0:00.28] [STAT:S] /Applications/Google Chrome.app/Contents/Frameworks/Google Chrome Framework.framework/Versions/117.0.5938.149/Helpers/Google Chrome Helper (Renderer).app/Contents/MacOS/Google Chrome Helper (Renderer) --type=renderer --disable-nacl --origin-trial-disabled-features=WebGPU --lang=en-GB --num-raster-threads=4 --enable-zero-copy --enable-gpu-memory-buffer-compositor-resources --enable-main-frame-before-activation --renderer-client-id=282 --time-ticks-at-unix-epoch=-1696577818407481 --launch-time-ticks=6326131680 --shared-files --field-trial-handle=1718379636,r,658519363837203859,834477175066415890,262144 --seatbelt-client=143
[…]
Requirements
This utility isn’t limited to Mac, it can be used on wathever *nix operating system with ps
and perl
(roughly whatever *nix).
In addition:
fuser
command, if available, is used to convert the full path of a file into the PID of the process that is using it.
- perl
Cwd
module if used to convert local path to absolute paths for fuser
.
If the module is missing you can still use absolute paths.
Install
Download the attached script, unzip it.
Then cd
to the expanded folder, and:
sudo cp -p pssearch /usr/local/bin
Source
#!/usr/bin/perl
# v. 1.3
# © 2023 faqintosh.com
my $arg = shift(@ARGV) || "";
if ( $arg eq "" ) {
die <<'ENDOFHELP';
Syntax: pssearch <argument>
Argument can be:
- a path If argument is a path to an existing file or folder then
whateve PID locking the resource (if any) will be reported.
- a number Report for such PID
- a string Running processes commands will be filtered case insensitive
for containing the string
- a regex As above, in the string basic regex interpolation is supported.
Regex, not wildcards: use "." (not "?") or ".+" (not "*")
Only one argument is supported.
Should the argument contain whitespaces wrap it within "..."
ENDOFHELP
}
my $all = getAllPs();
my @pids = byArg( $arg );
print formatOut($_) for @pids;
print "\n";
sub getAllPs {
my $all = {};
my @rows = split(/\n+/,`ps -ajx | grep -v pssearch`);
my @headers = split(/[ \t]+/,shift(@rows));
my $qtheads = $#headers;
for my $row (@rows) {
chomp $row;
my @vals = split(/[ \t]+/,$row);
my $ps = {};
for (my $i=0; $i <= $qtheads; $i++) {
my $k = $headers[$i];
my $v = $i < $qtheads ? shift(@vals) : join(' ',@vals);
$ps->{$k} = $v;
}
$all->{$ps->{PID}} = $ps if $ps->{PID};
}
return $all;
}
sub byFuser {
my $path = shift;
my $fuser = `which fuser`;
return { message => "fuser not found" } unless $fuser =~ m,^/,;
chomp $fuser;
if ( $path !~ m,^/,) {
eval 'use Cwd "abs_path"; $path = abs_path($path);';
return {
message => "Cwd perl module is missing - use full path",
error => "$@"
} if $@;
}
my $out = `"$fuser" -f "$path" 2>/dev/null`;
chomp $out;
return {
pid => $out,
message => "In use by PID $out",
path => $path
} if $out =~ m,^[0-9]+$,;
return {
message => "Not in use by any process",
path => $path
};
}
sub byArg {
my $arg = shift;
my @out;
if ( -e $arg) {
my $fu = byFuser($arg);
my $path = $fu->{path}||$arg;
print "\nPath: $path\n";
$arg = $fu->{pid} if $fu->{pid};
print " `--> $fu->{message}\n" if $fu->{message};
return @out if ( $fu->{path} and ! $fu->{pid} )
}
if ( $arg =~ m/^[0-9]+$/) {
push(@out,$all->{$arg}{PID});
} else {
foreach my $pid (keys %$all) {
my $ps = $all->{$pid};
push(@out,$pid) if $ps->{COMMAND} =~ m/$arg/i;
}
}
return @out;
}
sub makeTree {
my $pid = shift;
my $ps = $all->{$pid};
return "" unless $ps;
my $out = {};
$out->{PID} = $pid;
if ( $ps->{PPID} ) {
my $parent = makeTree( $ps->{PPID} );
if ( $parent ) {
$out->{parent} = $parent;
$out->{level} = $parent->{level} +1;
} else {
$out->{zombie} = 1;
$out->{level} = 0;
}
} else {
$out->{level} = 0;
}
return $out;
}
sub formatOut {
my $pid = shift;
my $sel = makeTree($pid);
my @lines;
@lines = formatSelEntry($sel,@lines);
return join("\n","",@lines,"");
};
sub formatSelEntry {
my $sel = shift;
my @lines = @_;
my $pid = $sel->{PID};
my $ps = $all->{ $pid };
die "PID not found: $pid\n" unless $ps;
my $out = "";
for my $flag (qw/PID USER TIME STAT/ ) {
$out .= "[$flag:$ps->{$flag}] " if $ps->{$flag};
}
if ( $sel->{level} ) {
my $pre = " `--> ";
if ( $sel->{level} > 1 ) {
my $qt = ( $sel->{level} -1 ) * 6;
$pre = (" " x $qt).$pre;
}
$out = $pre . $out;
}
$out .= '[ZOMBIE] ' if $sel->{zombie};
$out .= $ps->{COMMAND};
unshift @lines,$out;
@lines = formatSelEntry($sel->{parent},@lines) if $sel->{parent};
return @lines;
}