├── .gitmodules ├── .editorconfig ├── LICENSE ├── README.md └── run-parts /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "test/run-parts-test-suite"] 2 | path = test/run-parts-test-suite 3 | url = https://github.com/ikysil/run-parts-test-suite 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # run-parts 2 | Bash-based implementation of `run-parts` - concept taken from 3 | [Debian](http://www.unix.com/man-page/linux/8/run-parts/) 4 | with extensions from [Ubuntu](http://manpages.ubuntu.com/manpages/trusty/man8/run-parts.8.html) 5 | 6 | # Usage 7 | 8 | NAME 9 | run-parts - run scripts or programs in a directory 10 | 11 | SYNOPSIS 12 | run-parts [--test] [--verbose] [--report] [--umask=umask] [--lsbsysinit] [--regex=REGEX] 13 | [--arg=argument] [--exit-on-error] [--help] [--list] [--reverse] [--] DIRECTORY 14 | 15 | DESCRIPTION 16 | run-parts runs all the executable files named within constraints described below, found in 17 | directory directory. Other files and directories are silently ignored. 18 | 19 | If neither the --lsbsysinit option nor the --regex option is given then the names must 20 | consist entirely of ASCII upper- and lower-case letters, ASCII digits, ASCII underscores, 21 | and ASCII minus-hyphens. 22 | 23 | If the --lsbsysinit option is given, then the names must not end in .dpkg-old or 24 | .dpkg-dist or .dpkg-new or .dpkg-tmp, and must belong to one or more of the following 25 | namespaces: the LANANA-assigned namespace (^[a-z0-9]+$); the LSB hierarchical and reserved 26 | namespaces (^_?([a-z0-9_.]+-)+[a-z0-9]+$); and the Debian cron script namespace (^[a-zA- 27 | Z0-9_-]+$). 28 | 29 | If the --regex option is given, the names must match the custom extended regular 30 | expression specified as that option's argument. 31 | 32 | Files are run in the lexical sort order of their names unless the --reverse option is 33 | given, in which case they are run in the opposite order. 34 | 35 | OPTIONS 36 | --test print the names of the scripts which would be run, but don't actually run them. 37 | 38 | --list print the names of the all matching files (not limited to executables), but don't 39 | actually run them. This option cannot be used with --test. 40 | 41 | -v, --verbose 42 | print the name of each script to stderr before running. 43 | 44 | --report 45 | similar to --verbose, but only prints the name of scripts which produce output. 46 | The script's name is printed to whichever of stdout or stderr the script produces 47 | output on. The script's name is not printed to stderr if --verbose also specified. 48 | 49 | --reverse 50 | reverse the scripts' execution order. 51 | 52 | --exit-on-error 53 | exit as soon as a script returns with a non-zero exit code. 54 | 55 | --umask=umask 56 | sets the umask to umask before running the scripts. umask should be specified in 57 | octal. By default the umask is set to 022. 58 | 59 | --lsbsysinit 60 | filename must be in one or more of either the LANANA-assigned namespace, the LSB 61 | namespaces - either hierarchical or reserved - or the Debian cron script namespace. 62 | 63 | --regex=REGEX 64 | validate filenames against custom extended regular expression REGEX 65 | 66 | -a, --arg=argument 67 | pass argument to the scripts. Use --arg once for each argument you want passed. 68 | 69 | -- specifies that this is the end of the options. Any filename after -- will be not 70 | be interpreted as an option even if it starts with a hyphen. 71 | 72 | -h, --help 73 | display usage information and exit. 74 | -------------------------------------------------------------------------------- /run-parts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # run-parts - concept taken from Debian 4 | # http://www.unix.com/man-page/linux/8/run-parts/ 5 | # with extensions from Ubuntu 6 | # http://manpages.ubuntu.com/manpages/trusty/man8/run-parts.8.html 7 | 8 | # keep going when something fails 9 | set +e 10 | # pipefail is required for --report support 11 | set -o pipefail 12 | 13 | usage() { 14 | echo "run-parts [--test] [--verbose] [--report] [--umask=umask] [--lsbsysinit] [--regex=REGEX] 15 | [--arg=argument] [--exit-on-error] [--help] [--list] [--reverse] [--] DIRECTORY" 16 | } 17 | 18 | help() { 19 | echo " 20 | NAME 21 | run-parts - run scripts or programs in a directory 22 | 23 | SYNOPSIS 24 | run-parts [--test] [--verbose] [--report] [--umask=umask] [--lsbsysinit] [--regex=REGEX] 25 | [--arg=argument] [--exit-on-error] [--help] [--list] [--reverse] [--] DIRECTORY 26 | 27 | DESCRIPTION 28 | run-parts runs all the executable files named within constraints described below, found in 29 | directory directory. Other files and directories are silently ignored. 30 | 31 | If neither the --lsbsysinit option nor the --regex option is given then the names must 32 | consist entirely of ASCII upper- and lower-case letters, ASCII digits, ASCII underscores, 33 | and ASCII minus-hyphens. 34 | 35 | If the --lsbsysinit option is given, then the names must not end in .dpkg-old or 36 | .dpkg-dist or .dpkg-new or .dpkg-tmp, and must belong to one or more of the following 37 | namespaces: the LANANA-assigned namespace (^[a-z0-9]+$); the LSB hierarchical and reserved 38 | namespaces (^_?([a-z0-9_.]+-)+[a-z0-9]+$); and the Debian cron script namespace (^[a-zA- 39 | Z0-9_-]+$). 40 | 41 | If the --regex option is given, the names must match the custom extended regular 42 | expression specified as that option's argument. 43 | 44 | Files are run in the lexical sort order of their names unless the --reverse option is 45 | given, in which case they are run in the opposite order. 46 | 47 | OPTIONS 48 | --test print the names of the scripts which would be run, but don't actually run them. 49 | 50 | --list print the names of the all matching files (not limited to executables), but don't 51 | actually run them. This option cannot be used with --test. 52 | 53 | -v, --verbose 54 | print the name of each script to stderr before running. 55 | 56 | --report 57 | similar to --verbose, but only prints the name of scripts which produce output. 58 | The script's name is printed to whichever of stdout or stderr the script produces 59 | output on. The script's name is not printed to stderr if --verbose also specified. 60 | 61 | --reverse 62 | reverse the scripts' execution order. 63 | 64 | --exit-on-error 65 | exit as soon as a script returns with a non-zero exit code. 66 | 67 | --umask=umask 68 | sets the umask to umask before running the scripts. umask should be specified in 69 | octal. By default the umask is set to 022. 70 | 71 | --lsbsysinit 72 | filename must be in one or more of either the LANANA-assigned namespace, the LSB 73 | namespaces - either hierarchical or reserved - or the Debian cron script namespace. 74 | 75 | --regex=REGEX 76 | validate filenames against custom extended regular expression REGEX 77 | 78 | -a, --arg=argument 79 | pass argument to the scripts. Use --arg once for each argument you want passed. 80 | 81 | -- specifies that this is the end of the options. Any filename after -- will be not 82 | be interpreted as an option even if it starts with a hyphen. 83 | 84 | -h, --help 85 | display usage information and exit. 86 | " 87 | } 88 | 89 | report-and-pipe() { 90 | rline="$1" 91 | while IFS= read -r line; do 92 | echo -en "$rline" ; echo "$line"; 93 | unset rline; 94 | done; 95 | } 96 | 97 | if [ $# -lt 1 ]; then 98 | usage 99 | exit 1 100 | fi 101 | 102 | args="" 103 | dir="" 104 | umask="" 105 | 106 | for i in "$@"; do 107 | if [ ${append_arg:-0} = 1 ]; then 108 | args="$args $i" 109 | append_arg=0 110 | continue 111 | fi 112 | case $i in 113 | --list) 114 | list=1 115 | ;; 116 | --test) 117 | test=1 118 | ;; 119 | --verbose|-v) 120 | verbose=1 121 | ;; 122 | --report) 123 | report=1 124 | ;; 125 | --reverse) 126 | reverse=1 127 | ;; 128 | --lsbsysinit) 129 | lsbsysinit=1 130 | ;; 131 | --regex) 132 | regex="${i#*=}" 133 | ;; 134 | --arg=*) 135 | args="$args ${i#*=}" 136 | ;; 137 | -a) 138 | append_arg=1 139 | ;; 140 | --umask=*) 141 | umask="${i#*=}" 142 | ;; 143 | --help|-h) 144 | help 145 | exit 0 146 | ;; 147 | --exit-on-error) 148 | exit_on_error=1 149 | ;; 150 | --) 151 | # -- end of options 152 | ;; 153 | -*) 154 | echo Unknown argument: $i > /dev/stderr 155 | echo Rest of arguments: $* > /dev/stderr 156 | usage 157 | exit 1 158 | ;; 159 | *) 160 | # directory 161 | dir=$i 162 | break 163 | ;; 164 | esac 165 | done 166 | 167 | if [[ "x$dir" = "x" && ! -d "$dir" ]]; then 168 | echo "Not a directory: '$dir'" 169 | usage 170 | exit 1 171 | fi 172 | 173 | # Ignore *~ and *, scripts 174 | filelist=$(LC_ALL=C; ls -1 "${dir}" | grep -vEe '[~,]$') 175 | 176 | if [ ${reverse:-0} = 1 ]; then 177 | filelist=$(echo "$filelist" | sort -r) 178 | fi 179 | 180 | echo "$filelist" | while read bname ; do 181 | fpath="${dir%/}/${bname}" 182 | [ -d "${fpath}" ] && continue 183 | # Don't run *.{disabled,rpmsave,rpmorig,rpmnew,swp,cfsaved} scripts 184 | [ "${bname%.disabled}" != "${bname}" ] && continue 185 | [ "${bname%.cfsaved}" != "${bname}" ] && continue 186 | [ "${bname%.rpmsave}" != "${bname}" ] && continue 187 | [ "${bname%.rpmorig}" != "${bname}" ] && continue 188 | [ "${bname%.rpmnew}" != "${bname}" ] && continue 189 | [ "${bname%.swp}" != "${bname}" ] && continue 190 | [ "${bname%,v}" != "${bname}" ] && continue 191 | 192 | if [ ${lsbsysinit:-0} = 1 ]; then 193 | # Don't run *.{dpkg-old,dpkg-dist,dpkg-new,dpkg-tmp} scripts 194 | [ "${bname%.dpkg-old}" != "${bname}" ] && continue 195 | [ "${bname%.dpkg-dist}" != "${bname}" ] && continue 196 | [ "${bname%.dpkg-new}" != "${bname}" ] && continue 197 | [ "${bname%.dpkg-tmp}" != "${bname}" ] && continue 198 | # Adhere to LANANA-assigned LSB (hierarchical and reserved) and the Debian cron script namespaces 199 | [[ ! "${bname}" =~ ^[a-z0-9]+$ ]] && \ 200 | [[ ! "${bname}" =~ ^_?([a-z0-9_.]+-)+[a-z0-9]+$ ]] && \ 201 | [[ ! "${bname}" =~ ^[a-zA-Z0-9_-]+$ ]] && continue 202 | fi 203 | 204 | if [ "x$regex" != "x" ]; then 205 | [[ ! "${bname}" =~ $regex ]] && continue 206 | fi 207 | 208 | if [ -e "${fpath}" ]; then 209 | if [ -r $dir/whitelist ]; then 210 | grep -q "^${bname}$" $dir/whitelist && continue 211 | fi 212 | 213 | if [ ${list:-0} = 1 ]; then 214 | echo "${fpath}" $args; 215 | continue 216 | fi 217 | 218 | if [ -x "${fpath}" ]; then 219 | if [ ${test:-0} = 1 ]; then 220 | echo "${fpath}" $args; 221 | continue 222 | fi 223 | if [ "$RANDOMIZE" != "" ]; then 224 | let "rtime = $RANDOM" 225 | if [ "$RANDOMTIME" != "" ]; then 226 | let "rtime %= $RANDOMTIME" 227 | else 228 | let "rtime %= 300" 229 | fi 230 | sleep $rtime 231 | fi 232 | 233 | # run executable files 234 | if [ ${verbose:-0} = 1 ]; then 235 | echo "${fpath}" $args > /dev/stderr 236 | fi 237 | 238 | if [ "x$umask" != "x" ]; then 239 | umask $umask 240 | fi 241 | 242 | if [ ${report:-0} = 1 ]; then 243 | oline="${fpath}\n" 244 | # do not report script name over stderr in verbose mode 245 | # no duplicates are needed 246 | if [ ${verbose:-0} = 1 ]; then 247 | eline="" 248 | else 249 | eline="${fpath}\n" 250 | fi 251 | { "${fpath}" $args 2>&1 1>&3 3>&- | 252 | # handle stderr redirected to stdout 253 | report-and-pipe "$eline" 254 | } 3>&1 1>&2 | 255 | # handle stdout 256 | report-and-pipe "$oline" 257 | else 258 | "${fpath}" $args 259 | fi 260 | 261 | rc=${PIPESTATUS[0]} 262 | 263 | if [ ${verbose:-0} = 1 ]; then 264 | echo "${fpath}" $args exit status $rc > /dev/stderr 265 | fi 266 | 267 | if [ ${rc:-0} != 0 ]; then 268 | if [ ${exit_on_error:-0} = 1 ]; then 269 | exit $rc 270 | fi 271 | fi 272 | fi 273 | fi 274 | done 275 | 276 | exit 0 277 | --------------------------------------------------------------------------------