diff --git a/doxygen/Makefile.am b/doxygen/Makefile.am
index e788843..aa19c5a 100644
--- a/doxygen/Makefile.am
+++ b/doxygen/Makefile.am
@@ -25,201 +25,7 @@ doxyfile.stamp: $(doc_srcs) Makefile.am
 # but not blank lines
 
 if BUILD_MAN
-	/bin/bash -p -c 'declare -A renamed_page;\
-##
-main(){ set -e; cd man/man3; rm -f _*;\
-  count_real_pages;\
-  rename_real_pages;\
-  make_symlinks;\
-  post_process;\
-};\
-##
-count_real_pages(){ page_count=0;\
-  ##
-  ## Count "real" man pages (i.e. not generated by MAN_LINKS)
-  ## MAN_LINKS pages are 1-liners starting .so
-  ## Method: list files in descending order of size,
-  ## looking for the first 1-liner
-  ##
-  for i in $$(ls -S);\
-  do head -n1 $$i | grep -E -q '^\.so' && break;\
-    page_count=$$(($$page_count + 1));\
-  done;\
-  first_link=$$(($$page_count + 1));\
-};\
-##
-rename_real_pages(){ for i in $$(ls -S | head -n$$page_count);\
-  do for j in $$(ls -S | tail -n+$$first_link);\
-    do grep -E -q $$i$$ $$j && break;\
-    done;\
-    mv -f $$i $$j;\
-    renamed_page[$$i]=$$j;\
-  done;\
-};\
-##
-make_symlinks(){ for j in $$(ls -S | tail -n+$$first_link);\
-  do ln -sf $${renamed_page[$$(cat $$j | cut -f2 -d/)]} $$j;\
-  done;\
-};\
-##
-post_process(){ make_temp_files;\
-  ##
-  ## DIAGNOSTIC / DEVELOPMENT CODE
-  ## set -x and restrict processing to keep_me: un-comment to activate
-  ## Change keep_me as required
-  ##
-  ##keep_me=nfq_icmp_get_hdr.3;\
-  ##do_diagnostics;\
-  ##
-  ## Work through the "real" man pages
-  for target in $$(ls -S | head -n$$page_count);\
-  do mygrep "^\\.SH \"Function Documentation" $$target;\
-    ## Next file if this isn't a function page
-    [ $$linnum -ne 0 ] || continue;\
-    ##
-    del_modules;\
-    del_bogus_synopsis;\
-    fix_name_line;\
-    move_synopsis;\
-    del_empty_det_desc;\
-    del_def_at_lines;\
-    fix_double_blanks;\
-  done;\
-  ##
-  remove_temp_files;\
-};\
-##
-fix_double_blanks(){ linnum=1;\
-  ##
-  ## Older versions of man display a blank line on encountering "\fB\fP";
-  ## newer versions of man do not.
-  ## doxygen emits "\fB\fP" on seeing "\par" on a line by itself.
-  ## "\par" gives us double-spacing in the web doc, which we want, but double-
-  ## spacing looks odd in a man page so remove "\fB\fP".
-  ##
-  while [ $$linnum -ne 0 ];\
-  do mygrep \\\\fB\\\\fP $$target;\
-    [ $$linnum -eq 0 ] || delete_lines $$linnum $$linnum;\
-  done;\
-};\
-##
-del_def_at_lines(){ linnum=1;\
-  while [ $$linnum -ne 0 ];\
-  do mygrep "^Definition at line [[:digit:]]* of file" $$target;\
-    [ $$linnum -eq 0 ] || delete_lines $$(($$linnum - 1)) $$linnum;\
-  done;\
-};\
-##
-## Only invoked if you un-comment the 2 diagnostic / development lines above
-do_diagnostics(){ mv $$keep_me xxx;\
-  rm *.3;\
-  mv xxx $$keep_me;\
-  page_count=1;\
-  set -x;\
-};\
-##
-del_empty_det_desc(){ mygrep "^\\.SH \"Function Documentation" $$target;\
-  i=$$linnum;\
-  mygrep "^\\.SH \"Detailed Description" $$target;\
-  [ $$linnum -ne 0  ] || return 0;\
-  [ $$(($$i - $$linnum)) -eq 3 ] || return 0;\
-  delete_lines $$linnum $$(($$i -1));\
-};\
-##
-move_synopsis(){ mygrep "SH SYNOPSIS" $$target;\
-  [ $$linnum -ne 0  ] || return 0;\
-  i=$$linnum;\
-  ## If this is a doxygen-created synopsis, leave it.
-  ## (We haven't inserted our own one in the source yet)
-  mygrep "^\\.SS \"Functions" $$target;\
-  [ $$i -gt $$linnum ] || return 0;\
-  ##
-  mygrep "^\\.SH \"Function Documentation" $$target;\
-  j=$$(($$linnum - 1));\
-  head -n$$(($$j - 1)) $$target | tail -n$$(($$linnum - $$i - 1)) >$$fileC;\
-  delete_lines $$i $$j;\
-  mygrep "^\\.SS \"Functions" $$target;\
-  head -n$$(($$linnum - 1)) $$target >$$fileA;\
-  tail -n+$$(($$linnum + 1)) $$target >$$fileB;\
-  cat $$fileA $$fileC $$fileB >$$target;\
-};\
-##
-fix_name_line(){ all_funcs="";\
-  ##
-  ## Search a shortened version of the page in case there are .RI lines later
-  mygrep "^\\.SH \"Function Documentation" $$target;\
-  head -n$$linnum $$target >$$fileC;\
-  ##
-  while :;\
-  do mygrep ^\\.RI $$fileC;\
-    [ $$linnum -ne 0 ] || break;\
-    ## Discard this entry
-    tail -n+$$(($$linnum + 1)) $$fileC >$$fileB;\
-    cp $$fileB $$fileC;\
-    ##
-    func=$$(cat $$fileG | cut -f2 -d\\ | cut -c3-);\
-    [ -z "$$all_funcs" ] && all_funcs=$$func ||\
-      all_funcs="$$all_funcs, $$func";\
-  done;\
-  ## For now, assume name is at line 5
-  head -n4 $$target >$$fileA;\
-  desc=$$(head -n5 $$target | tail -n1 | cut -f3- -d" ");\
-  tail -n+6 $$target >$$fileB;\
-  cat $$fileA >$$target;\
-  echo "$$all_funcs \\- $$desc" >>$$target;\
-  cat $$fileB >>$$target;\
-};\
-##
-del_modules(){ mygrep "^\.SS \"Modules" $$target;\
-  [ $$linnum -ne 0  ] || return 0;\
-  i=$$linnum;\
-  mygrep "^\\.SS \"Functions" $$target;\
-  delete_lines $$i $$(($$linnum - 1));\
-};\
-##
-del_bogus_synopsis(){ mygrep "SH SYNOPSIS" $$target;\
-  ##
-  ## doxygen 1.8.20 inserts its own SYNOPSIS line but there is no mention
-  ## in the documentation or git log what to do with it.
-  ## So get rid of it
-  ##
-  [ $$linnum -ne 0  ] || return 0;\
-  i=$$linnum;\
-  ## Look for the next one
-  tail -n+$$(($$i + 1)) $$target >$$fileC;\
-  mygrep "SH SYNOPSIS" $$fileC;\
-  [ $$linnum -ne 0  ] || return 0;\
-  ##
-  mygrep "^\\.SS \"Functions" $$target;\
-  delete_lines $$i $$(($$linnum - 1));\
-};\
-##
-## Delete lines $1 through $2 from $target
-delete_lines(){ head -n$$(($$1 - 1)) $$target >$$fileA;\
-  tail -n+$$(($$2 +1)) $$target >$$fileB;\
-  cat $$fileA $$fileB >$$target;\
-};\
-##
-mygrep(){ set +e;\
-  grep -En "$$1" $$2 2>/dev/null >$$fileH;\
-  [ $$? -ne 0 ] && linnum=0 ||\
-    { head -n1 $$fileH >$$fileG; linnum=$$(cat $$fileG | cut -f1 -d:); };\
-  set -e;\
-};\
-##
-make_temp_files(){ temps="A B C G H";\
-  for i in $$temps;\
-  do declare -g file$$i=$$(mktemp);\
-  done;\
-};\
-##
-remove_temp_files(){ for i in $$temps;\
-  do j=file$$i;\
-    rm $${!j};\
-  done;\
-};\
-##
-main'
+	$(abs_top_srcdir)/doxygen/build_man.sh
 endif
 
 	touch doxyfile.stamp
@@ -245,3 +51,5 @@ endif
 uninstall-local:
 	rm -rf $(DESTDIR)$(htmldir) $(DESTDIR)$(mandir) man html doxyfile.stamp
 endif
+
+EXTRA_DIST = build_man.sh
diff --git a/doxygen/build_man.sh b/doxygen/build_man.sh
new file mode 100755
index 0000000..da8c84e
--- /dev/null
+++ b/doxygen/build_man.sh
@@ -0,0 +1,215 @@
+#!/bin/bash -p
+
+declare -A renamed_page
+
+main(){
+  set -e
+  cd man/man3; rm -f _*
+  count_real_pages
+  rename_real_pages
+  make_symlinks
+  post_process
+}
+
+count_real_pages(){
+  page_count=0
+  #
+  # Count "real" man pages (i.e. not generated by MAN_LINKS)
+  # MAN_LINKS pages are 1-liners starting .so
+  # Method: list files in descending order of size,
+  # looking for the first 1-liner
+  #
+  for i in $(ls -S)
+  do head -n1 $i | grep -E -q '^\.so' && break
+    page_count=$(($page_count + 1))
+  done
+  first_link=$(($page_count + 1))
+}
+
+rename_real_pages(){
+  for i in $(ls -S | head -n$page_count)
+  do for j in $(ls -S | tail -n+$first_link)
+    do grep -E -q $i$ $j && break
+    done
+    mv -f $i $j
+    renamed_page[$i]=$j
+  done
+}
+
+make_symlinks(){
+  for j in $(ls -S | tail -n+$first_link)
+  do ln -sf ${renamed_page[$(cat $j | cut -f2 -d/)]} $j
+  done
+}
+
+post_process(){
+  make_temp_files
+  #
+  # DIAGNOSTIC / DEVELOPMENT CODE
+  # set -x and restrict processing to keep_me: un-comment to activate
+  # Change keep_me as required
+  #
+  #keep_me=nfq_icmp_get_hdr.3;\
+  #do_diagnostics;\
+  #
+  # Work through the "real" man pages
+  for target in $(ls -S | head -n$page_count)
+  do mygrep "^\\.SH \"Function Documentation" $target
+    # Next file if this isn't a function page
+    [ $linnum -ne 0 ] || continue
+
+    del_modules
+    del_bogus_synopsis
+    fix_name_line
+    move_synopsis
+    del_empty_det_desc
+    del_def_at_lines
+    fix_double_blanks
+  done
+
+  remove_temp_files
+}
+
+fix_double_blanks(){
+  linnum=1
+  #
+  # Older versions of man display a blank line on encountering "\fB\fP";
+  # newer versions of man do not.
+  # doxygen emits "\fB\fP" on seeing "\par" on a line by itself.
+  # "\par" gives us double-spacing in the web doc, which we want, but double-
+  # spacing looks odd in a man page so remove "\fB\fP".
+  #
+  while [ $linnum -ne 0 ]
+  do mygrep \\\\fB\\\\fP $target
+    [ $linnum -eq 0 ] || delete_lines $linnum $linnum
+  done
+}
+
+del_def_at_lines(){
+  linnum=1
+  while [ $linnum -ne 0 ]
+  do mygrep "^Definition at line [[:digit:]]* of file" $target
+    [ $linnum -eq 0 ] || delete_lines $(($linnum - 1)) $linnum
+  done
+}
+
+# Only invoked if you un-comment the 2 diagnostic / development lines above
+do_diagnostics(){
+  mv $keep_me xxx
+  rm *.3
+  mv xxx $keep_me
+  page_count=1
+  set -x
+}
+
+del_empty_det_desc(){
+  mygrep "^\\.SH \"Function Documentation" $target
+  i=$linnum
+  mygrep "^\\.SH \"Detailed Description" $target
+  [ $linnum -ne 0  ] || return 0
+  [ $(($i - $linnum)) -eq 3 ] || return 0
+  delete_lines $linnum $(($i -1))
+}
+
+move_synopsis(){
+  mygrep "SH SYNOPSIS" $target
+  [ $linnum -ne 0  ] || return 0
+  i=$linnum
+  # If this is a doxygen-created synopsis, leave it.
+  # (We haven't inserted our own one in the source yet)
+  mygrep "^\\.SS \"Functions" $target
+  [ $i -gt $linnum ] || return 0
+
+  mygrep "^\\.SH \"Function Documentation" $target
+  j=$(($linnum - 1))
+  head -n$(($j - 1)) $target | tail -n$(($linnum - $i - 1)) >$fileC
+  delete_lines $i $j
+  mygrep "^\\.SS \"Functions" $target
+  head -n$(($linnum - 1)) $target >$fileA
+  tail -n+$(($linnum + 1)) $target >$fileB
+  cat $fileA $fileC $fileB >$target
+}
+
+fix_name_line(){
+  all_funcs=""
+
+  # Search a shortened version of the page in case there are .RI lines later
+  mygrep "^\\.SH \"Function Documentation" $target
+  head -n$linnum $target >$fileC
+
+  while :
+  do mygrep ^\\.RI $fileC
+    [ $linnum -ne 0 ] || break
+    # Discard this entry
+    tail -n+$(($linnum + 1)) $fileC >$fileB
+    cp $fileB $fileC
+
+    func=$(cat $fileG | cut -f2 -d\\ | cut -c3-)
+    [ -z "$all_funcs" ] && all_funcs=$func ||\
+      all_funcs="$all_funcs, $func"
+  done
+  # For now, assume name is at line 5
+  head -n4 $target >$fileA
+  desc=$(head -n5 $target | tail -n1 | cut -f3- -d" ")
+  tail -n+6 $target >$fileB
+  cat $fileA >$target
+  echo "$all_funcs \\- $desc" >>$target
+  cat $fileB >>$target
+}
+
+del_modules(){
+  mygrep "^\.SS \"Modules" $target
+  [ $linnum -ne 0  ] || return 0
+  i=$linnum
+  mygrep "^\\.SS \"Functions" $target
+  delete_lines $i $(($linnum - 1))
+}
+
+del_bogus_synopsis(){
+  mygrep "SH SYNOPSIS" $target
+  #
+  # doxygen 1.8.20 inserts its own SYNOPSIS line but there is no mention
+  # in the documentation or git log what to do with it.
+  # So get rid of it
+  #
+  [ $linnum -ne 0  ] || return 0
+  i=$linnum
+  # Look for the next one
+  tail -n+$(($i + 1)) $target >$fileC;\
+  mygrep "SH SYNOPSIS" $fileC
+  [ $linnum -ne 0  ] || return 0
+
+  mygrep "^\\.SS \"Functions" $target
+  delete_lines $i $(($linnum - 1))
+}
+
+# Delete lines $1 through $2 from $target
+delete_lines(){
+  head -n$(($1 - 1)) $target >$fileA
+  tail -n+$(($2 +1)) $target >$fileB
+  cat $fileA $fileB >$target
+}
+
+mygrep(){
+  set +e
+  grep -En "$1" $2 2>/dev/null >$fileH
+  [ $? -ne 0 ] && linnum=0 ||\
+    { head -n1 $fileH >$fileG; linnum=$(cat $fileG | cut -f1 -d:); }
+  set -e
+}
+
+make_temp_files(){
+  temps="A B C G H"
+  for i in $temps
+  do declare -g file$i=$(mktemp)
+  done
+}
+
+remove_temp_files(){
+  for i in $temps
+  do j=file$i
+    rm ${!j}
+  done
+}
+
+main
