Generate a PDF Copy of the Man Page for a Shell Command

Thanks !
I hadn’t seen that font cache event - I wonder if there’s a simple way of trapping it …

UPDATE:
I’ve added an -l switch to pstopdf to the macro in the post above – I think that should redirect any messages about font initialisation to a log file …

CMD="$KMVAR_Shell_command";
man -t  "$CMD" | pstopdf -l -i -o $HOME/Desktop/$CMD.pdf;
open -a Preview $HOME/Desktop/$CMD.pdf

FWIW a version which lists 1000+ commands and lets you select which ones you want to see PDF man pages for:

Browse Bash command man page(s).kmmacros (2.9 KB)

osascript -l JavaScript <<JXA_END 2>/dev/null
function run() {
  function d(a) {
    return a = ['man -t  "', '" | pstopdf -l -i -o $HOME/Desktop/', ".pdf; open -a Preview $HOME/Desktop/", ".pdf"].join(a);
  }
  var c = Application.currentApplication(), b = Application("System Events");
  c.includeStandardAdditions = !0;
  b.includeStandardAdditions = !0;
  b.activate();
  var a = c.doShellScript('man -k . | grep "(1)" | cut -d "(" -f 1 | sort -f | uniq -u').split("\r"), a = b.chooseFromList(a, {withTitle:"BASH man pages", withPrompt:"Choose 1 or more from " + a.length.toString() + " commands :", okButtonName:"Read", multipleSelectionsAllowed:!0}), a = a instanceof Array ? a : [];
  lstNoEntry = [];
  a.forEach(function(a) {
    try {
      return c.doShellScript(d(a));
    } catch (b) {
      lstNoEntry.push("no manual entry for: " + a);
    }
  });
  lstNoEntry.length && (b.activate(), b.displayAlert(lstNoEntry.join("\n"), {message:"no manual entry found"}));
  return !0;
}
JXA_END
1 Like

Extended version of the first macro above (enter shell command name to view man page PDF), which simply:

  1. Keeps the generated PDFs in a folder (prompts you on first use to choose or make a folder),
  2. checks, when you ask for a man page, whether the corresponding PDF already exists, and either opens the existing one or creates a new one, and
  3. remembers the last command that you asked for.

Shell manual page for command (as PDF) copy.kmmacros (9.5 KB)

1 Like

And an updated version of the second macro above (browse and select from 1000+ commands to read PDF man pages) which:

  1. Uses the same PDF folder as the other script, and
  2. opens any existing PDF for a given command, rather than creating a new copy,
  3. remembers the previous selection(s) from either script.

Browse Bash command man page(s).kmmacros (10.0 KB)

This version currently does not allow asking for the man page of things like “5 vpnd”

Subtly adjusting the quoting resolves the problem:

CMD="$KMVAR_Shell_command";
man -t  $CMD | pstopdf -l -i -o "$HOME/Desktop/$CMD.pdf"
open -a Preview "$HOME/Desktop/$CMD.pdf"

By removing the quoting around the $CMD in the man, you can have the two parameters, and by quoting the $CMD in the path and the open, the file name can have a space in it.

Speaking of which, technically $HOME can have a space in it too, so it should always be quoted - this lead to a significant data loss bug in an iTunes update for folks with this issue.

2 Likes

UPDATE - the version I am using now, which aims to include those quote adjustments:

Shell manual page for command (as PDF) copy.kmmacros (32.1 KB)

Hello, I have downloaded your last version of "Browse Bash command man page(s).kmmacros", but after setting a folder as asked, the macro does not create nor show a pdf of a man command. To understand what happens, I have changed the last Execute Shell script of the macro from "ignore result" to "display results in a window".

Here is the error output:

/var/folders/tq/242wr3411rl03x6czq91h4b00000gn/T/Keyboard-Maestro-Script-391AA1A3-86CF-4179-86EB-721EF954B392: eval: line 5: syntax error near unexpected token `('
/var/folders/tq/242wr3411rl03x6czq91h4b00000gn/T/Keyboard-Maestro-Script-391AA1A3-86CF-4179-86EB-721EF954B392: eval: line 5: `echo /Users/(myName)/Documents/Man page PDFs (via Keyboard Maestro)'
pstopdf failed on PS read from stdin with error code 30
See log file "ls.pdf.log" for messages
The file /ls.pdf does not exist.

The folder "/Users/(myName)/Documents/Man page PDFs (via Keyboard Maestro)" is empty.

Can you help, please? (by the way, the second macro " Shell manual page for command (as PDF) copy.kmmacros" doesn't work neither, nothing is displayed).

The second version is working here under Catalina.

Which macOS version are you using there?

The trick would probably be to get something working on the command line in Terminal.app, and then gradually re-wrap a macro around it.

For example, can you at least get something in Terminal.app by requesting the pstopdf command line options – entering this incantation:

pstopdf --help

and a view of the man page with:

man pstopdf

If those both fire OK, then try writing a man page PDF to the Desktop with something like:

man -t tr | pstopdf -l -o "$HOME/Desktop/tr.pdf"

and if that seems to work OK then

open "$HOME/Desktop/tr.pdf"

If all of those things have functioned OK, then perhaps the issue is with the destination folder ?

Let me know where you get to ...


One thing that might be worth experimenting with in the last Shell script action of the macro:

IFS=' '; read -a CMDSET <<< "$KMVAR_Shell_command";
for CMD in "${CMDSET[@]}"; do
	MANPDF="$CMD.pdf"
	# Allow for expansion of $HOME etc in path string
	cd "$(eval echo $KMVAR_man_page_PDF_folder)";
	if [ ! -e $MANPDF ]; then
		man -t $CMD | pstopdf -l -i -o $MANPDF
	fi
	open -a Preview "$MANPDF"
done

is to eliminate the -i flag from the main line, editing it to:

man -t $CMD | pstopdf -l -o $MANPDF

Here's my current version (working on Catalina), with the -i flag suppressed:

Shell manual page for command (as PDF).kmmacros (32.1 KB)

1 Like

Thanks for your answer. I'm under Big Sur (11.2.1).

pstopdf --help

gave:

pstopdf: illegal option -- -
Usage: pstopdf [inputfile] [-o outname] [-l] [-p]
Try: man pstopdf

The next:

man pstopdf

is OK. Then with:

man -t tr | pstopdf -l -o "$HOME/Desktop/tr.pdf"

I obtain 2 files, the expected tr.pdf and a tr.pdf.log (it contain a list of fonts). The next command:

open "$HOME/Desktop/tr.pdf"

is also OK. The pdf is opened in Preview.app.

Your new version of "Shell manual page for command (as PDF).kmmacros" don't work neither.

So, I go to the Keyboard Maestro preferences to delete the "man page PDF folder", and retry with your new version, and set the destination folder to ~/Documents/man-page-PDF, and it works.

My previous destination folder was ~/Documents/Man page PDFs (via Keyboard Maestro).

Doesn't your macro support path with spaces in it?

But, the second macro "Browse Bash command man page(s).kmmacros" don't work (nothing displayed). To try understand why it don't work, I modified the last "Execute shell script" to change form "ignore results" to "display results in a window". But no window is displayed. In last resort, I add an "Alert" action before the last "Execute shell script" and the alert is displayed as expected. So what can I do with the Javascript code to test what is wrong?

I don't think the macro contains any JavaScript – the shell script issues are a bit hard to test when we are using different OS versions (and possibly different shells ?)

Did I understand from your narrative above:

So, I go to the Keyboard Maestro preferences to delete the "man page PDF folder", and retry with your new version, and set the destination folder to ~/Documents/man-page-PDF , and it works.

That you have managed to find a working permutation of the options ?

In "Browse Bash command man page(s).kmmacros", aren't in the code:

osascript -l JavaScript <<JXA_END 2>/dev/null
(function(strFolderVar) {
  function l(a, b) {
    b = b || strFolderVar;

a JavaScript code?

The macro "Shell manual page for command (as PDF).kmmacros" run yet perfectly by changing the destination folder from ~/Documents/Man page PDFs (via Keyboard Maestro) to ~/Documents/man-page-PDF. So I think there was a problem because my first destination folder contained spaces in the name.

I have never used the Script Editor.app with the Javascript language. What must I paste to the Script Editor.app (with JavaScript instead AppleScript setting in the window) to test the code? I tried with all the code in the "Execute shell script" action, except the first and the last line (so no osascript -l JavaScript <<JXA_END 2>/dev/null nor JXA_END) and the script compile, and display a window with a big list of 245 commands.
So why doesn't run it in the macro?

(text edited: previously, when pasted in the Script Editor.app, I forgot a ( at the beginning).

Got it. There is indeed a JS fragment in there, but it's still part of a Shell script.

(a literal string argument to the shell osascript command)

I could take a look later in the week at allowing for paths with space. Many thanks for pointing it out.

(Incidentally, if you ever do want to experiment with some JS code in Script Editor, the trick is to choose JavaScript from the language selector at top left)

Screenshot 2021-02-21 at 17.44.46

Yes, it's exactly what I have do (but as my language is not English in my country, I badly explain it).

As the (JavaScript) code run (*) in Script Editor.app, the problem is in the first line:

osascript -l JavaScript <<JXA_END 2>/dev/null 

or in the last line:

JXA_END

(*) in the Script Editor.app, the code display a window with 245 commands (but not the ls command!). If I select the "openssl" in the list, I obtain a window titled: "no manual entry for: openssl".
Same error for "ps2pdf".
But theses error are perhaps I run the code in the Script Editor.app and not in the macro via the shell.

Yes – as I was trying, probably not very clearly, to explain - that isn't JavaScript – that's a shell command.

No more time today, alas, but I will take a look at all this in the week : - )

After checking with @ComplexPoint, the solution was found. I don't know why, but in the System Preferences, on "Security and confidentiality", at "Automatisation", for Keyboard Maestro.app, "System Events.app" wasn't checked. After checking it, all run good.

Sorry for this.

I'm using this for the man pages, but how can I adapt it to accept other Terminal output, such as "transcode-video --help"? Thanks!

The PDF is derived, in that macro, from the Postscript output which man can generate if we choose its -t option.

You would only be able to pull off a similar trick with other shell commands which can generate a formatted version in Postscript – you won't be able to do it with plain text output streams.

(I had to correct this a couple of times, kindly look at the final version, please.)

Thanks!

Enscript (installed using HomeBrew) is a possibility, if "enscript.cfg" is modified so that the line

DefaultOutputMethod: printer

is changed to

DefaultOutputMethod: stdout

In that case, a pipe such as

enscript --filter="transcode-video --help" | pstopdf -l -o ~/desktop/onefile.pdf

is successful.

However, substituting

enscript --filter=$CMD | pstopdf -l -o $MANPDF

for

man -t $CMD | pstopdf -l -o $MANPDF

in the macro didn't work, apparently because "transcode-video --help" when entered into the dialog, is interpreted as two separate commands.

So that's one issue to be solved (my proficiency is mainly limited to AppleScript), and the second is to fork between the two command versions based on a conditional (should be easy to trap an error when either executable doesn't receive correct input).

I still used to get better results with "open -f -a /Applications/Preview.app" (before prepending the app path with "/System" was required).

"| open -f -a /Applications/Safari.app" is still beneficial to some extent, however.

Skip the path – use:

open -f -b com.apple.Safari

I don't know anything about enscript, so you're on your own with that.

-Chris