Runs a script for each file in a directory hierarchy using GNU parallel
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

141 lines
5.5 KiB

6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
3 years ago
6 years ago
6 years ago
5 years ago
6 years ago
5 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
5 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
6 years ago
  1. #!/bin/bash
  2. # set shell options
  3. set -e # abort on first error
  4. shopt -s nullglob # allow filename patterns which match no files to expand to a null string, rather than themselves
  5. shopt -s dotglob
  6. # define queue
  7. queue=()
  8. function queueecho {
  9. for item in "${queue[@]}"; do
  10. echo "$item"
  11. done
  12. }
  13. function queueexec {
  14. queueecho | parallel "-j$parallelcount" --eta "eval {}"
  15. }
  16. function iteratedirs {
  17. local dir="$1"
  18. local currentlevel="$2"
  19. local current_rel_dir="$3"
  20. for item in "$dir"/*; do
  21. if [[ -d $item ]]; then
  22. if [[ $depth == none ]] || [[ $currentlevel -lt $depth ]]; then
  23. iteratedirs "${item}" $(($currentlevel + 1)) "${current_rel_dir}${item##*/}/"
  24. fi
  25. elif [[ -f $item ]]; then
  26. if [[ ! $filter ]] || [[ $item =~ $filter ]]; then
  27. name=${item##*/}
  28. namewithoutextension=${name%.*}
  29. iteratortargetdir=${targetdir}/${current_rel_dir}
  30. queue+=("ITERATOR_FULL_PATH=${item@Q} ITERATOR_FILE_NAME=${name@Q} ITERATOR_FILE_NAME_WITHOUT_EXTENSION=${namewithoutextension@Q} ITERATOR_CURRENT_DIR=${dir@Q} ITERATOR_CURRENT_REL_DIR=${current_rel_dir@Q} ITERATOR_BASE_DIR=${basedir@QQ} ITERATOR_TARGET_DIR=${iteratortargetdir@Q} ${cmd@Q} ${append@Q}")
  31. else
  32. echo "${bold}${blue}Info:${normal} ${bold}Skipping »$item«.${normal}"
  33. fi
  34. else
  35. echo "${bold}${yellow}Warning:${normal} ${bold}Item »$item« is neither a directory nor a file and will be skipped.${normal}"
  36. fi
  37. done
  38. }
  39. # determine sequences for formatted output
  40. red=$(tput setaf 1)
  41. green=$(tput setaf 2)
  42. yellow=$(tput setaf 3)
  43. blue=$(tput setaf 4)
  44. bold=$(tput bold)
  45. normal=$(tput sgr0)
  46. # parse arguments
  47. read= argcount=0 append= basedir=. targetdir= depth=none cmd= filter= parallelcount=+0 noconfirm=
  48. for arg in "$@"
  49. do
  50. if [[ $arg == --base-dir ]]; then
  51. read=basedir
  52. elif [[ $arg == --depth ]]; then
  53. read=depth
  54. elif [[ $arg == --cmd ]]; then
  55. read=cmd
  56. elif [[ $arg == --filter ]]; then
  57. read=filter
  58. elif [[ $arg == --args ]]; then
  59. read=arguments
  60. elif [[ $arg == --parallel-count ]]; then
  61. read=parallelcount
  62. elif [[ $arg == --target-dir ]]; then
  63. read=target
  64. elif [[ $arg == --no-confirm ]]; then
  65. noconfirm=true
  66. elif [[ $arg == --help ]] || [[ $arg == -h ]]; then
  67. echo "${bold}Runs a script for each file in a directory hierarchy using GNU parallel.${normal}
  68. --base-dir base directory (current directory by default)
  69. --target-dir target directory (base directory by default)
  70. --depth maximal recursion depth (unlimited by default)
  71. --cmd command to be executed
  72. --filter regular expression to filter files, eg. ${bold}.*\.((mp4$)|(mp3$))${normal}
  73. --args arguments to be passed to cmd
  74. --no-confirm generated commands will be executed without prompt for confirmation
  75. --parallel-count maximal number of commands to be executed parallel
  76. ${bold}The following environment variables will be set when running the script:${normal}
  77. ITERATOR_FULL_PATH current file path
  78. ITERATOR_FILE_NAME current file name with extension
  79. ITERATOR_FILE_NAME_WITHOUT_EXTENSION current file name without extension
  80. ITERATOR_CURRENT_DIR current directory
  81. ITERATOR_BASE_DIR base directory (specified using --base-dir)
  82. ITERATOR_CURRENT_REL_DIR current directory (relative to the base directory)
  83. ITERATOR_TARGET_DIR target directory for the current file
  84. "
  85. exit 0
  86. else
  87. if [[ $read == arguments ]]; then
  88. append="$append \"$arg\""
  89. elif [[ $read == basedir ]]; then
  90. basedir=$arg
  91. elif [[ $read == depth ]]; then
  92. if ! [[ $arg =~ ^[0-9]+$ ]]; then
  93. echo "${bold}${red}Error:${normal} ${bold}specified depth »$arg« is not an unsigned number.${normal}"
  94. exit 1
  95. fi
  96. depth=$arg
  97. elif [[ $read == cmd ]]; then
  98. cmd=$arg
  99. elif [[ $read == filter ]]; then
  100. filter=$arg
  101. elif [[ $read == parallelcount ]]; then
  102. parallelcount=$arg
  103. elif [[ $read == target ]]; then
  104. targetdir=$arg
  105. else
  106. echo "${bold}${red}Error:${normal} ${bold}Invalid argument »$arg« specified.${normal}"
  107. exit 1
  108. fi
  109. if [[ $read != arguments ]]; then
  110. read=
  111. fi
  112. fi
  113. done
  114. # validate specified arguments, use base directory as target directory if not specified
  115. [[ $targetdir ]] || targetdir=$basedir
  116. if [[ ! $cmd ]]; then
  117. echo "${bold}${red}Error:${normal} ${bold}No command specified.${normal}"
  118. exit 1
  119. fi
  120. # start recursive iteration and exec queue
  121. iteratedirs "$basedir" 0
  122. if [[ ${#queue[@]} -ge 1 ]]; then
  123. echo "${bold}Generated queue${normal}"
  124. queueecho
  125. if [[ $noconfirm ]]; then
  126. queueexec
  127. else
  128. while true; do
  129. read -p "${bold}Do you want to execute ${#queue[@]} commands [y/n]?${normal} " yn
  130. case $yn in
  131. [Yy]*) queueexec; break;;
  132. [Nn]*) exit;;
  133. *) echo "${bold}Please answer yes or no.${normal}";;
  134. esac
  135. done
  136. fi
  137. else
  138. echo "${bold}${yellow}Warning:${normal} ${bold}Queue is empty.${normal}"
  139. fi