#!/bin/bash # set shell options set -e # abort on first error shopt -s nullglob # allow filename patterns which match no files to expand to a null string, rather than themselves shopt -s dotglob # define queue queue=() function queueecho { for item in "${queue[@]}"; do echo "$item" done } function queueexec { queueecho | parallel "-j$parallelcount" --eta "eval {}" } function iteratedirs { local dir="$1" local depth="$2" local current_rel_dir="$3" for item in "$dir"/*; do if [[ -d $item ]]; then if [[ $depth == none ]] || [[ $currentlevel -lt $depth ]]; then iteratedirs "${item}" $(($depth + 1)) "${current_rel_dir}${item##*/}/" fi elif [[ -f $item ]]; then if [[ ! $filter ]] || [[ $item =~ $filter ]]; then name=${item##*/} namewithoutextension=${name%.*} queue+=("ITERATOR_FULL_PATH=\"$item\" ITERATOR_FILE_NAME=\"$name\" ITERATOR_FILE_NAME_WITHOUT_EXTENSION=\"$namewithoutextension\" ITERATOR_CURRENT_DIR=\"$dir\" ITERATOR_CURRENT_REL_DIR=\"$current_rel_dir\" ITERATOR_BASE_DIR=\"$basedir\" ITERATOR_TARGET_DIR=\"$targetdir/$current_rel_dir\" \"$cmd\" $append") else echo "${bold}${blue}Info:${normal} ${bold}Skipping »$item«.${normal}" fi else echo "${bold}${yellow}Warning:${normal} ${bold}Item »$item« is neither a directory nor a file and will be skipped.${normal}" fi done } # determine sequences for formatted output red=$(tput setaf 1) green=$(tput setaf 2) yellow=$(tput setaf 3) blue=$(tput setaf 4) bold=$(tput bold) normal=$(tput sgr0) # parse arguments read= argcount=0 append= basedir=. targetdir= depth=none cmd= filter= parallelcount=+0 noconfirm= for arg in "$@" do if [[ "--base-dir" == $arg ]]; then read=basedir elif [[ "--depth" == $arg ]]; then read=depth elif [[ "--cmd" == $arg ]]; then read=cmd elif [[ "--filter" == $arg ]]; then read=filter elif [[ "--args" == $arg ]]; then read=arguments elif [[ "--parallel-count" == $arg ]]; then read=parallelcount elif [[ "--target-dir" == $arg ]]; then read=target elif [[ "--no-confirm" == $arg ]]; then noconfirm=true elif [[ "--help" == $arg ]] || [[ "-h" == $arg ]]; then echo "${bold}Runs a script for each file in a directory hierarchy using GNU parallel.${normal} --base-dir the base directory (./ by default) --target-dir the target directory (base directory by default) --depth the maximal recursion depth (unlimited by default) --cmd the command to be executed --filter a regular expression to filter files, eg. ${bold}.*\.((mp4$)|(mp3$))${normal} --args the arguments to be passed to cmd --no-confirm generated commands will be executed without prompt for confirmation --parallel-count the maximal number of commands to be executed parallel ${bold}The following environment variables will be set when running the script:${normal} ITERATOR_FULL_PATH Path of the current file. ITERATOR_FILE_NAME Full name of the current file. ITERATOR_FILE_NAME_WITHOUT_EXTENSION Name of the current file without extension. ITERATOR_CURRENT_DIR Path of the current directory. ITERATOR_BASE_DIR Path of the base directory (specified using --base-dir). ITERATOR_CURRENT_REL_DIR Path of the current directory (relative to the base directory). ITERATOR_TARGET_DIR Path of the target directory for the current file (specified --target-dir + ITERATOR_CURRENT_REL_DIR). " exit 0 else if [[ $read == arguments ]]; then append="$append \"$arg\"" elif [[ $read == basedir ]]; then basedir=$arg elif [[ $read == depth ]]; then if ! [[ $arg =~ ^[0-9]+$ ]]; then echo "${bold}${red}Error:${normal} ${bold}specified depth »$arg« is not an unsigned number.${normal}" exit 1 fi depth=$arg elif [[ $read == cmd ]]; then cmd=$arg elif [[ $read == filter ]]; then filter=$arg elif [[ $read == parallelcount ]]; then parallelcount=$arg elif [[ $read == target ]]; then targetdir=$arg else echo "${bold}${red}Error:${normal} ${bold}Invalid argument »$arg« specified.${normal}" exit 1 fi if [[ $read != arguments ]]; then read= fi fi done # validate specified arguments, use base directory as target directory if not specified [[ $targetdir ]] || targetdir=$basedir if [[ ! $cmd ]]; then echo "${bold}${red}Error:${normal} ${bold}No command specified.${normal}" exit 1 fi # start recursive iteration and exec queue iteratedirs "$basedir" 0 if [[ ${#queue[@]} -ge 1 ]]; then echo "${bold}Generated queue${normal}" queueecho if [[ $noconfirm ]]; then queueexec else while true; do read -p "${bold}Do you want to execute ${#queue[@]} commands [y/n]?${normal} " yn case $yn in [Yy]*) queueexec; break;; [Nn]*) exit;; *) echo "${bold}Please answer yes or no.${normal}";; esac done fi else echo "${bold}${yellow}Warning:${normal} ${bold}Queue is empty.${normal}" fi