h3xx

optimg

Jul 19th, 2011
894
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. #!/bin/sh
  2.  
  3. # optimize image files for size
  4.  
  5. # CHANGES:
  6. #
  7. # 2011-1211-1210 :
  8. #   * Add `-C' option to not reduce the color type. I've encountered some
  9. #     programs that will not read images with a color palette (namely
  10. #     stella and sometimes even gimp). This method is preferable to using
  11. #     the PREFER_PNGCRUSH method.
  12. #
  13. # 2011-1124-1225 :
  14. #   * Added support for using pngcrush over optipng(1) via the environment
  15. #     variable `PREFER_PNGCRUSH'. pngcrush may not work as well, but it
  16. #     still has its merits as it doesn't insist on re-factoring the color
  17. #     map, a process which can cause errors in some programs.
  18. #
  19. # 2011-0814-1808 :
  20. #   * Added support for omitting the preservation of timestamps via the
  21. #     `-T' option.
  22. #   * Fixed bug in reporting section that was causing individual filenames
  23. #     not to be printed.
  24. #   * Errs out if it can't preserve EXIF information. This is because the
  25. #     user specifically requests to save this information, and if we can't
  26. #     do it, the user would most likely be pissed off we optimized the
  27. #     image but dumped the EXIF.
  28. #
  29. # 2011-0802-2333 :
  30. #   * Commented out MNG support, as advmng(1) seems to kill the image's
  31. #     usability.
  32. #
  33. # 2011-0802-2308 :
  34. #   * [REDACTED] Now supports optimizing MNG files via advmng(1).
  35. #
  36. # 2011-0725-2201 :
  37. #   * Tests for the existence of jhead(1), jpegtran(1) and gifsicle(1) as
  38. #     they aren't necessarily commonly-distributed programs. jpegtran(1)
  39. #     has the highest probability of being installed as it is part of the
  40. #     libjpeg package.
  41. #   * Specifying `-r' will now recursively process ONLY directories and
  42. #     not specifying it will process ONLY files. Perhaps one day, that
  43. #     piped file list I mention in BUGS will allow both, but for now
  44. #     recursive and non-recursive are mutually exclusive.
  45. #   * Fixed bugs relating to recursing directories starting with a hyphen.
  46. #   * More efficient file size counting in file_size() via better stat(1)
  47. #     arguments.
  48. #   * Fixed text formatting of the help message.
  49. #   * Hardier cpu core count detection for if the kernel hackers ever
  50. #     change the format of /proc/cpuinfo. This also fixes a potentially
  51. #     dangerous bug related to piping a big list of files to xargs(1) when
  52. #     `--max-procs=0', which will cause xargs to process ALL the jobs at
  53. #     once.
  54. #   * Short-circuited the parallel processing operation if the number of
  55. #     jobs/CPU cores is equal to 1.
  56. #   * Recursive operation actually works well now.
  57. #   * More GNU-style messages.
  58. #
  59. # 2011-0718-2328 :
  60. #   * More and better documentation. Now all the functions have header
  61. #     documentation.
  62. #   * Longer options to incidental program calls.
  63. #   * Numerous warnings about recursion pitfalls and inherent bugs.
  64. #   * Bug workaround:
  65. #       pngcrush(1), jpegtran(1) and jhead(1) have no way of accepting
  66. #       file arguments starting with a dash (`-'). This is not my
  67. #       fault.
  68. #     Now the filename is passed through readlink(1) so that it only
  69. #     processes absolute paths, which [should] never start with a hyphen.
  70. #   * Workarounds for a few minor security holes.
  71. #   * Outputs human-readable sizes.
  72. #   * Short-circuited the file_size() function so it's not doing shell math
  73. #     where it doesn't need to.
  74. #   * Recursive operation via find(1) (recursive_selfcall()) now self-calls
  75. #     with `-q' if it was specified when starting the script.
  76. #
  77. # 2010-0715-1300 :
  78. #   * Now counts freed bytes via stat(1).
  79. #   * Included -q option for quiet operation.
  80. #   * Included -r and -x EXTENSION option for recursive scheduling.
  81. #     Recursive scheduling now supercedes normal scheduling.
  82. #   * Removed -s option for single files (previously used when scheduling);
  83. #     now relies on value of `$#' to determine whether more than one file
  84. #     was indicated on the command line.
  85. #
  86. # 2010-0321-2044 :
  87. #   * Now uses parallel processes via xargs(1).
  88. #
  89. # 2010-0125-2015 :
  90. #   * Fixed some bugs/modularity issues regarding local variables in
  91. #     functions.
  92. #   * Included support for [more advanced] optipng(1). It is now preferred
  93. #     over pngcrush(1) as it seems to do a better job.
  94. #
  95. # BUGS:
  96. #   * Doesn't do that great a job of counting freed bytes on larger
  97. #     operations using the `-r' (recursive) flag. This is due to find(1)
  98. #     sending only-so-many file names to the command when using the `-exec
  99. #     [COMMAND] [ARGS] {} +' syntax. Perhaps implementing a piped file
  100. #     list would solve it.
  101. #   * hr_size() function does not round its figures; it only truncates
  102. #     them. This is due to its only using the built-in shell math
  103. #     functionality, which is only capable of integer math.
  104. #
  105.  
  106. HELP_MESSAGE() {
  107.     local EXIT_CODE="${1:-0}"
  108.     cat <<EOF
  109. Usage: $(basename -- "$0") [OPTIONS] [--] FILE...
  110.        $(basename -- "$0") [OPTIONS] -r [--] [DIR]...
  111. Optimize image files for size.
  112.  
  113.   -h        Show this help message.
  114.   -e        Preserve Exif metadata in JPEG files using jhead(1).
  115.   -j NUM    Run at maximum NUM simultaneous operations. This defaults to
  116.           the auto-detected number of processors/virtual cores in the
  117.           system.
  118.   -q        Be quiet. Produce only error messages on STDERR.
  119.   -r        Optimize all images found by recursing DIRs. File arguments
  120.           are ignored.
  121.   -x SUF    When optimizing recursively via the \`-r' flag, only operate
  122.           on files whose names match /.*SUF/. May be specified
  123.           multiple times. Also accepts POSIX-compatible regular
  124.           expressions. If not specified, recursive operations will
  125.           operate on all files.
  126.   -T        Do not preserve timestamp information on optimized images.
  127.           Default is to preserve as much as possible.
  128.   -C        Be careful when optimizing PNGs. Sometimes reducing a PNG's
  129.           color map can cause it to be displayed incorrectly. Most
  130.           programs can handle it, however.
  131.   --        Terminate options list.
  132.  
  133. Currently, this script will only work on the following types of files:
  134.   * JPEG (\`image/jpeg')                         Using jpegtran(1)
  135.   * Portable Network Graphics (\`image/png')     Using optipng(1)/pngcrush
  136.   * Mult-image Network Graphics (\`video/x-mng') Using advmng(1) [not working]
  137.   * Graphics Interchange Format (\`image/gif')   Using gifsicle(1)
  138.  
  139.   If the environment variable PREFER_PNGCRUSH is set to a non-empty value, then
  140.   pngcrush is preferred over optipng(1).
  141.  
  142. Copyright (C) 2010-2011 Dan Church.
  143. License GPLv3+: GNU GPL version 3 or later (http://gnu.org/licenses/gpl.html).
  144. This is free software: you are free to change and redistribute it. There is NO
  145. WARRANTY, to the extent permitted by law.
  146. EOF
  147.     exit "$EXIT_CODE"
  148. }
  149.  
  150. KEEP_EXIF=0
  151. # determine number of jobs from number of CPU cores in the system
  152. NUM_JOBS="$(
  153.     cpudetect=0
  154.     if [ -r '/proc/cpuinfo' ]; then
  155.         cpudetect="$(
  156.             grep -c '^processor' '/proc/cpuinfo'
  157.         )"
  158.     fi
  159.     if [ "$cpudetect" -gt 0 ]; then
  160.         echo "$cpudetect"
  161.     else
  162.         echo 1
  163.     fi
  164. )"
  165. QUIET=0
  166. RECURSIVE=0
  167. EXTS=()
  168. PRESERVE_TIMESTAMP=1
  169. CAREFUL=0
  170.  
  171. # XXX : Note to self: when adding options, make sure to update
  172. #   recursive_selfcall() and schedule_process()
  173. while getopts 'hej:qrx:TC-' flag; do
  174.     case "$flag" in
  175.         'e')
  176.             KEEP_EXIF=1
  177.             ;;
  178.         'j')
  179.             NUM_JOBS="$OPTARG"
  180.             ;;
  181.         'q')
  182.             QUIET=1
  183.             ;;
  184.         'r')
  185.             RECURSIVE=1
  186.             ;;
  187.         'x')
  188.             EXTS+=("$OPTARG")
  189.             ;;
  190.         'T')
  191.             PRESERVE_TIMESTAMP=0
  192.             ;;
  193.         'C')
  194.             CAREFUL=1
  195.             ;;
  196.         'h')
  197.             HELP_MESSAGE 0
  198.             ;;
  199.         *)
  200.             HELP_MESSAGE 1
  201.             ;;
  202.     esac
  203. done
  204.  
  205. shift "$((OPTIND-1))"
  206.  
  207. TEMP_FILES=()
  208.  
  209. cleanup() {
  210.     rm -f -- "${TEMP_FILES[@]}"
  211. }
  212.  
  213. trap 'cleanup'  EXIT
  214.  
  215. # prints out the sum of the sizes, in bytes of all files passed to it
  216. file_size() {
  217.     if [ "$#" -gt 1 ]; then
  218.         # adds all the file sizes by doing a stat on all of them
  219.         # (separated by `\n'), transforming them into
  220.         # `LINE+LINE+...LINE+0' and passing them to the Bash expression
  221.         # evaluator
  222.         echo "$(($(
  223.             stat \
  224.                 --printf='%s+' \
  225.                 --dereference \
  226.                 -- \
  227.                 "$@" 2>/dev/null
  228.         )0))"
  229.     else
  230.         # only got one file to check; do it simply
  231.         stat \
  232.             --format='%s' \
  233.             --dereference \
  234.             -- \
  235.             "$@" 2>/dev/null
  236.     fi
  237. }
  238.  
  239. # produces a human-readable size from the byte count passed to it
  240. hr_size() {
  241.     local \
  242.         bytes="$1" \
  243.         units \
  244.         fact \
  245.         thresh \
  246.         decimals \
  247.         exp \
  248.         hr_val \
  249.         hr_unit \
  250.  
  251.     #units=(B KB MB GB TB PB EB ZB YB) # shell math can only go so far...
  252.     units=(B KB MB GB TB)
  253.     fact=1024
  254.     thresh=9/10
  255.     decimals=1
  256.  
  257.     # cycle through units from largest to smallest, exiting when it finds
  258.     # the largest applicable unit
  259.     for ((exp = ${#units[@]} - 1; exp > -1; --exp)); do
  260.         # check if the unit is close enough to the unit's size, within
  261.         # the threshold
  262.         if [ "$bytes" -gt "$((fact**exp*${thresh}))" ]; then
  263.             # we found the applicable unit
  264.  
  265.             # must multiply by a factor of 10 here to not truncate
  266.             # the given number of decimal places after the point
  267.             hr_val="$((bytes*10**decimals/fact**exp))"
  268.  
  269.             # put the decimal point in
  270.             if [ "$decimals" -gt 0 ]; then
  271.                 # left: divide and truncate
  272.                 # right: modulus
  273.                 hr_val="$((hr_val/10**decimals)).$((hr_val%10**decimals))"
  274.             fi
  275.  
  276.             hr_unit="${units[$exp]}"
  277.             break
  278.         fi
  279.     done
  280.  
  281.     if [ -z "$hr_unit" ]; then
  282.         hr_val="$bytes"
  283.         hr_unit="${units[0]}"
  284.     fi
  285.  
  286.     echo "${hr_val} ${hr_unit}"
  287. }
  288.  
  289. # prints out the simple mimetype (e.g. `image/jpeg') of a file's contents
  290. get_mimetype() {
  291.     # XXX : file(1) doesn't always produce the correct mimetype for files;
  292.     #   possible workaround would be to include
  293.     #   `--magic-file /etc/file/magic/images'
  294.     file \
  295.         --preserve-date \
  296.         --dereference \
  297.         --brief \
  298.         --mime-type \
  299.         -- \
  300.         "$@" 2>/dev/null
  301. }
  302.  
  303. # copies $2 over to $1 if $2 is smaller than $1
  304. use_smaller() {
  305.     # if `$temp' isn't empty and it's of a smaller size than `$file',
  306.     # preserve every attribute and replace `$file' with `$temp'
  307.     local \
  308.         file="$1" \
  309.         temp="$2" \
  310.         origsize \
  311.         tempsize
  312.    
  313.     origsize="$(file_size "$file")"
  314.     tempsize="$(file_size "$temp")"
  315.  
  316.     if [ -f "$temp" -a \
  317.         "$tempsize" -gt 0 -a \
  318.         "$tempsize" -lt "$origsize" ]; then
  319.  
  320.         # preserve attributes by copying them from the original file to
  321.         # the temporary one
  322.         chmod \
  323.             --reference="$file" \
  324.             -- \
  325.             "$temp" &&
  326.  
  327.         (if [ "$PRESERVE_TIMESTAMP" -ne 0 ]; then
  328.             touch \
  329.                 --reference="$file" \
  330.                 -- \
  331.                 "$temp" ||
  332.                 exit "$?"
  333.         fi) &&
  334.  
  335.         (if [ "$UID" -eq 0 ]; then
  336.             # we're root, so we can chown(1) things
  337.             chown \
  338.                 --reference="$file" \
  339.                 -- \
  340.                 "$temp" ||
  341.  
  342.                 exit "$?"
  343.         fi) &&
  344.  
  345.         cp \
  346.             --preserve=mode,ownership,timestamps \
  347.             -- \
  348.             "$temp" \
  349.             "$file" &&
  350.  
  351.         local err="$?"
  352.         case "$err" in
  353.             '0')
  354.                 echo "optimized image \`$file'" >&2
  355.                 ;;
  356.             *)
  357.                 echo "failed to optimize \`$file'!" >&2
  358.                 ;;
  359.         esac
  360.  
  361.     fi
  362.  
  363.     # protect against unsuccessful following file writes to our temp file
  364.     rm \
  365.         --force \
  366.         -- \
  367.         "$temp"
  368.  
  369. }
  370.  
  371. # optimize a single[*] image file--any image file it can
  372. #
  373. # *: can handle multiple files
  374. optimize_image() {
  375.     local \
  376.         image_file \
  377.         temp
  378.  
  379.     for image_file; do
  380.  
  381.         [ -f "$image_file" ] || continue
  382.  
  383.         # get the absolute path of the image file so that ALL filenames
  384.         # can be handled, even ones beginning with a hyphen
  385.         image_file="$(readlink -f -- "$image_file")"
  386.  
  387.         temp="$(mktemp -t "$(basename -- "$0").XXXXXX")"
  388.         TEMP_FILES+=("$temp")
  389.  
  390.         case "$(get_mimetype "$image_file")" in
  391.  
  392. 'image/jpeg')
  393.  
  394.     # XXX : jpegtran and jhead can't handle input files starting with `-'
  395.     (if [ -n "$(type -Pt 'jpegtran')" ]; then
  396.         jpegtran \
  397.             -optimize \
  398.             -outfile "$temp" \
  399.             "$image_file" >&2 ||
  400.  
  401.             exit "$?"
  402.     else
  403.         echo 'No supported JPEG optimizer found.' >&2
  404.         exit 1
  405.     fi) &&
  406.  
  407.     (if [ "$KEEP_EXIF" -ne 0 ]; then
  408.         if [ -n "$(type -Pt 'jhead')" ]; then
  409.             # copy EXIF information from original file to the
  410.             # temporary file
  411.             # note: there is no long option for `-te'
  412.             jhead \
  413.                 -te "$image_file" \
  414.                 "$temp" >&2 ||
  415.  
  416.                 exit "$?"
  417.         else
  418.             echo 'Error: Cannot preserve EXIF information.' >&2
  419.             # the user specifically requested to save EXIF
  420.             # information, so if we can't do it, might as well
  421.             # give an error
  422.             exit 1
  423.         fi
  424.     fi) &&
  425.  
  426.     use_smaller "$image_file" "$temp"
  427.  
  428.     ;;
  429.  
  430. 'image/png')
  431.  
  432.     # truth table:
  433.     # prefpc| has_pngcrush  | has_optipng   | method
  434.     # 0 | 0     | 0     | exit
  435.     # 0 | 0     | 1     | optipng
  436.     # 0 | 1     | 0     | pngcrush
  437.     # 0 | 1     | 1     | optipng
  438.     # 1 | 0     | 0     | exit
  439.     # 1 | 0     | 1     | optipng
  440.     # 1 | 1     | 0     | pngcrush
  441.     # 1 | 1     | 1     | pngcrush
  442.     #
  443.     # optipng = (!prefpc -o !has_pngcrush) && has_optipng
  444.     (if [ \( -z "$PREFER_PNGCRUSH" -o -z "$(type -Pt 'pngcrush')" \) -a -n "$(type -Pt 'optipng')" ]; then
  445.  
  446.         # XXX : optipng refuses to overwrite files (even though the man
  447.         #   page says `-force' overrides this)
  448.         rm -f -- "$temp" &&
  449.  
  450.         optipng \
  451.             $([ "$QUIET" -eq 0 ] || echo ' -quiet') \
  452.             $([ "$CAREFUL" -eq 0 ] || echo ' -nc') \
  453.             -o7 \
  454.             -fix \
  455.             -force \
  456.             -out "$temp" \
  457.             -- \
  458.             "$image_file" >&2 ||
  459.  
  460.             exit "$?"
  461.  
  462.     elif [ -n "$(type -Pt 'pngcrush')" ]; then
  463.  
  464.         # XXX : pngcrush can't handle input files starting with `-'
  465.         pngcrush \
  466.             $([ "$QUIET" -eq 0 ] || echo '-q') \
  467.             -brute \
  468.             "$image_file" \
  469.             "$temp" >&2 ||
  470.  
  471.             exit "$?"
  472.  
  473.     else
  474.         echo 'No supported PNG optimizer found.' >&2
  475.         exit 1
  476.     fi) &&
  477.  
  478.     use_smaller "$image_file" "$temp"
  479.  
  480.     ;;
  481.  
  482. 'video/x-mng')
  483.  
  484.     #(if [ -n "$(type -Pt 'advmng')" ]; then
  485.     #   # advmng optimizes files in-place, but we want to be more
  486.     #   # careful than that
  487.     #   cp -- "$image_file" "$temp" &&
  488.     #
  489.     #   advmng \
  490.     #       --recompress \
  491.     #       --shrink-fast \
  492.     #       --force \
  493.     #       $([ "$QUIET" -eq 0 ] || echo '--verbose') \
  494.     #       -- \
  495.     #       "$temp" ||
  496.     #
  497.     #       exit "$?"
  498.     #
  499.     #   # XXX : sometimes advmng fucks up the MNG so work around
  500.     #   #   it by having it re-check its work
  501.     #   advmng \
  502.     #       --list \
  503.     #       -- \
  504.     #       "$temp" ||
  505.     #
  506.     #       exit "$?"
  507.     #
  508.     #else
  509.     #   echo 'No supported MNG optimizer found' >&2
  510.     #   exit 1
  511.     #fi) &&
  512.     #
  513.     #use_smaller "$image_file" "$temp"
  514.  
  515.     # FIXME : advmng is really, REALLY old and is kind of shit at the whole
  516.     #     not-fucking-up-your-images thing
  517.     echo 'Sorry, support for MNG is not yet implemented.' >&2
  518.  
  519.     ;;
  520.  
  521. 'image/gif')
  522.  
  523.     (if [ -n "$(type -Pt 'gifsicle')" ]; then
  524.         # XXX : this opens up a [very minor] symlink attack vector: if
  525.         #   "$temp" is replaced with a symlink during execution,
  526.         #   its target will be clobbered with the output from
  527.         #   gifsicle(1)
  528.         rm --force -- "$temp" &&
  529.  
  530.         gifsicle \
  531.             --optimize=3 \
  532.             --output "$temp" \
  533.             "$image_file" >&2 ||
  534.  
  535.             exit "$?"
  536.     else
  537.         echo 'No supported GIF optimizer found.' >&2
  538.         exit 1
  539.     fi) &&
  540.  
  541.     use_smaller "$image_file" "$temp"
  542.  
  543.     ;;
  544.  
  545. *)
  546.  
  547.     echo "Image type of \`$image_file' not recognized." >&2
  548.     ;;
  549.  
  550.         esac
  551.     done
  552. }
  553.  
  554. # process multiple files in parallel processes
  555. #
  556. # schedules the filenames passed to the function to execute in parallel by
  557. # calling this script for each file
  558. #
  559. # this function only runs if there are multiple files specified on the
  560. # command line
  561. #
  562. # XXX : it is VERY IMPORTANT that the self-calls initiated here are given only
  563. #   one file argument or this script will loop recursively forever
  564. schedule_process() {
  565.     local \
  566.         xargs_opts \
  567.         self_args \
  568.         file
  569.  
  570.     # intial options
  571.     xargs_opts=(
  572.         '--null'        # accept null-terminated list
  573.         '--max-args=1'      # send only one list item for each
  574.                     # process (VERY IMPORTANT!!!)
  575.         "--max-procs=$NUM_JOBS" # execute on parallel process per core
  576.         '--'            # terminate option list
  577.     )
  578.  
  579.     self_args=()
  580.  
  581.     if [ "$KEEP_EXIF" -ne 0 ]; then
  582.         self_args+=('-e')
  583.     fi
  584.  
  585.     if [ "$PRESERVE_TIMESTAMP" -eq 0 ]; then
  586.         self_args+=('-T')
  587.     fi
  588.  
  589.     if [ "$QUIET" -ne 0 ]; then
  590.         self_args+=('-q')
  591.     fi
  592.  
  593.     if [ "$CAREFUL" -ne 0 ]; then
  594.         self_args+=('-C')
  595.     fi
  596.  
  597.     self_args+=('--')   # terminate options list
  598.  
  599.     self_exec=(
  600.         "$0"        # call self
  601.         "${self_args[@]}"
  602.     )
  603.  
  604.     # add the self-execution to what xargs(1) executes
  605.     xargs_opts+=(
  606.         "${self_exec[@]}"
  607.     )
  608.  
  609.     # translate into null-separated list for maximum xargs security
  610.     for file; do
  611.         #echo -n "$file"$'\0'   # <~ this won't work
  612.         echo -n "$file"
  613.         echo -ne '\0'
  614.     done |
  615.  
  616.     # pipe it to xargs which will perform the scheduling, passing one
  617.     # file to another running copy of this script
  618.     # XXX : it is VERY important that only ONE file get passed or the
  619.     #   script will get stuck in an infinite recursion loop
  620.     xargs "${xargs_opts[@]}"
  621. }
  622.  
  623. # process a single file
  624. #
  625. # should run only if the script is given only one argument, which happens when
  626. # the script is self-executed via xargs(1) from the schedule_process() function
  627. single_process() {
  628.     optimize_image "$@"
  629. }
  630.  
  631. # process directories recursively
  632. #
  633. # sends all[*] files, optionally matching a given regex, located in the
  634. # directories given to a recursive call to this script, whereby the
  635. # schedule_process() function receives them and processes them in parallel
  636. #
  637. # *: does not send all files at once; there ARE system limits you know...
  638. #    but that doesn't mean that not all files get processed--the list just
  639. #    gets split up and sent to multiple self-invocations
  640. recursive_selfcall() {
  641.     local \
  642.         dirnames \
  643.         dir \
  644.         find_params \
  645.         self_exec \
  646.         self_args
  647.  
  648.     # transform directories passed in into absolute paths in order to
  649.     # correctly process directory arguments beginning with a hyphen
  650.     dirnames=()
  651.     for dir; do
  652.         dirnames+=("$(readlink -f -- "$dir")")
  653.     done
  654.  
  655.     # intial options
  656.     find_params=(
  657.         '-P'        # don't dereference symlinks
  658.         "${dirnames[@]:-.}" # search directories (expands to either
  659.                     # quoted arguments to this function, or
  660.                     # `.')
  661.         '-type' 'f'
  662.     )
  663.  
  664.     # add regex to match file extensions if specified with `-x'
  665.     if [ "${#EXTS[*]}" -gt 0 ]; then
  666.         find_params+=(
  667.             '-regextype'    'posix-egrep'
  668.             '-regex'    '.*\.('"${EXTS[*]/#.}"')'
  669.         )
  670.     fi
  671.  
  672.     self_args=(
  673.         '-j'    "$NUM_JOBS" # always-set option
  674.     )
  675.  
  676.     # pass option switches to self (except for `-r' and `-x`)
  677.     if [ "$KEEP_EXIF" -ne 0 ]; then
  678.         self_args+=('-e')
  679.     fi
  680.  
  681.     if [ "$PRESERVE_TIMESTAMP" -eq 0 ]; then
  682.         self_args+=('-T')
  683.     fi
  684.  
  685.     if [ "$QUIET" -ne 0 ]; then
  686.         self_args+=('-q')
  687.     fi
  688.  
  689.     if [ "$CAREFUL" -ne 0 ]; then
  690.         self_args+=('-C')
  691.     fi
  692.  
  693.     self_args+=('--')   # terminate options list
  694.  
  695.     self_exec=(
  696.         '-exec' "$0"    # the self-call
  697.         "${self_args[@]}"
  698.         '{}'    '+' # make sure our clone gets sent all the
  699.                 # matching files
  700.     )
  701.  
  702.     find_params+=(
  703.         "${self_exec[@]}"
  704.     )
  705.  
  706.     # now the call to find(1) is simple! (haha)
  707.     find "${find_params[@]}"
  708. }
  709.  
  710. # separate directory arguments from files
  711. arg_dirs=()
  712. arg_files=()
  713. for arg; do
  714.     if [ -d "$arg" ]; then
  715.         arg_dirs+=("$arg")
  716.     elif [ -f "$arg" ]; then
  717.         arg_files+=("$arg")
  718.     else
  719.         echo "Unknown filetype \`$arg'" >&2
  720.     fi
  721. done
  722.  
  723. # if only a single filename was passed in, then we're [most likely] being
  724. # called from xargs; otherwise, we're scheduling
  725.  
  726. if [ "$RECURSIVE" -ne 0 ]; then
  727.     if [ "$QUIET" -eq 0 ]; then
  728.         recursive_selfcall "${arg_dirs[@]}"
  729.     else
  730.         recursive_selfcall "${arg_dirs[@]}" >/dev/null 2>&1
  731.     fi
  732. else
  733.     # no arguments; print out help message like the friendlier
  734.     # command-line utilities
  735.     if [ "$#" -eq 0 ]; then
  736.         HELP_MESSAGE 0
  737.     fi
  738.  
  739.     if [ "${#arg_files[@]}" -eq 0 ]; then
  740.         echo 'No files to process.' >&2
  741.         if [ "${#arg_dirs[@]}" -ne 0 ]; then
  742.             echo '(Use `-r'\'' to process directories.)' >&2
  743.         fi
  744.         # obligatory stereotypical GNU-style message
  745.         echo "Try \`$(basename -- "$0") -h' for more information." >&2
  746.         exit 2
  747.     fi
  748.  
  749.     begin_filesize="$(file_size "${arg_files[@]}")"
  750.  
  751.     if [ "${#arg_files[@]}" -eq 1 -o "$NUM_JOBS" -eq 1 ]; then
  752.  
  753.         # only one file or one job specified; no need to run parallel
  754.         # processes
  755.         single_process "${arg_files[@]}"
  756.  
  757.         if [ "${#arg_files[@]}" -eq 1 ]; then
  758.             report_name="${arg_files[0]}"
  759.         else
  760.             report_name='all'
  761.         fi
  762.  
  763.     else
  764.         # multiple files specified; process them in parallel
  765.  
  766.         # schedule jobs
  767.         if [ "$QUIET" -eq 0 ]; then
  768.             schedule_process "${arg_files[@]}"
  769.         else
  770.             schedule_process "${arg_files[@]}" >/dev/null 2>&1
  771.         fi
  772.  
  773.         report_name='all'
  774.  
  775.     fi
  776.  
  777.     end_filesize="$(file_size "${arg_files[@]}")"
  778.  
  779.     freed="$((begin_filesize-end_filesize))"
  780.     freed_hr="$(hr_size "$freed")"
  781.  
  782.     # note: this must occur on the same line, because otherwise it gets
  783.     # separated during asynchronous operations
  784.     echo "${report_name}: freed ${freed} bytes (${freed_hr})"
  785. fi
RAW Paste Data