How can I programmatically determine which files are currently open in evince?
TLDR:
for ip in $(pgrep -x evince); do lsof -F +p $ip|grep -i '^n.*\.pdf$'|sed s/^n//g; done
Explanation:
Document Viewer
is the friendly name for the program /usr/bin/evince
. So first we need to find the process ID (PID) of evince
:
$ pgrep -x evince
22291
To list all files opened by this PID we will use the lsof
command (note that we'll need to repeat this for every PID in case we have more than one instance of evince running)
$ lsof -F +p 22291
some other files opened
.
.
.
n/home/c0rp/File.pdf
Next we'll grep only for pdfs and discard the irrelevant n at start of line:
$ lsof -Fn +p 22291 | grep -i '^n.*\.pdf$' | sed s/^n//g
/home/c0rp/File.pdf
Finally to combine everything in one bash line:
for ip in $(pgrep -x evince); do lsof -F +p $ip|grep -i '^n.*\.pdf$'|sed s/^n//g; done
This one-liner was inspired from the answer of terdon which is also very interesting in the way it solves the same problem.
If you are interested in what n in lsof -Fn
is for, here is quote from man lsof
about the -F
option:
OUTPUT FOR OTHER PROGRAMS
When the -F option is specified, lsof produces output that is suitable
for processing by another program - e.g, an awk or Perl script, or a C
program.
...
...
These are the fields that lsof will produce. The single character
listed first is the field identifier.
...
...
n file name, comment, Internet address
...
...
so -Fn
, is saying show me file name, comment, Internet address
Another approach would be something like
$ for ip in $(pgrep -x evince); do lsof -F +p $ip | grep -oP '^n\K.*\.pdf$'; done
/home/terdon/file1.pdf
/home/terdon/file2.pdf
Explanation
In general, whenever you want to search fro a process, pgrep
is better than ps -ef | grep process
since the latter will also match the grep
process itself. For example:
$ ps -ef | grep emacs
terdon 6647 6424 23 16:26 pts/14 00:00:02 emacs
terdon 6813 6424 0 16:26 pts/14 00:00:00 grep --color emacs
$ pgrep emacs
6647
The -x
option returns only processes whose entire name matches the string passed. This is needed because evince
also starts a daemon (evinced
) and that will also be matched without the -x
(the -l
is to print the name as well as the PID):
$ pgrep -l evince
4606 evince
4611 evinced
4613 evince
$ pgrep -lx evince
4606 evince
4613 evince
So, the for loop will run lsof
on each of the PIDs returned by pgrep
. These are then passed through grep
. The -o
option means "print only the matched portion of the line" and the -P
activates Perl Compatible Regular Expressions which lets us use \K
. In PCREs, \K
means "discard everything matched before the \K ". In other words, since I am using
-o, it will match lines beginning with
nand ending with
.pdfbut it will not print the matched
n`. The result is that only the file's name is printed.