#!/bin/bash # Copyright (C) 2018 Washington University School of Medicine # # Permission is hereby granted, free of charge, to any person obtaining # a copy of this software and associated documentation files (the # "Software"), to deal in the Software without restriction, including # without limitation the rights to use, copy, modify, merge, publish, # distribute, sublicense, and/or sell copies of the Software, and to # permit persons to whom the Software is furnished to do so, subject to # the following conditions: # # The above copyright notice and this permission notice shall be included # in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. set -ue global_cmd_line="$0 $*" global_script_name="wb_shortcuts" global_script_version="beta-0.5" function version () { echo "$global_script_name, version $global_script_version" } function usage () { version echo echo "Information options:" echo " -help display this help info" echo " -version display version info" echo " -list-functions show available functions" echo " -all-functions-help show all functions and their help info - VERY LONG" echo #wrap guide for 80 columns | echo "To get the help information of a function, run it without any additional" echo " arguments." echo echo "If the first argument is not recognized, all functions that start with the" echo " argument are displayed." echo } #the main function is so that we can put the main code before other function definitions #it simply gets called as 'main "$@"' after all functions are defined function main () { #NOTE: if we have a lot of functions, check_functions could cause a short startup delay, could make a debug setting to control it check_functions if (($# < 1)) then usage exit 0 fi case "$1" in (-version) version ;; (-help) usage ;; (-list-functions) list_functions ;; (-all-functions-help) all_functions_help ;; (*) #magic switch matching and function name conversion happens in here do_operation "$@" ;; esac } #automagic helpers for function matching #NOTE: mac still ships with a version of bash from 2007(!) #so, don't use associative arrays or anything fancy declare -a global_switch declare -a global_descrip function create_function () { local function_switch="$1" shift if [[ "$function_switch" == *' '* ]] then echo "ASSERT FAILURE: switch '$function_switch' contains space(s)" exit 1 fi if [[ "$function_switch" != "-"* ]] then echo "ASSERT FAILURE: switch '$function_switch' has no leading dash" exit 1 fi if [[ "$*" == "" ]] then echo "ASSERT FAILURE: switch '$function_switch' has empty short description" exit 1 fi #use $* in case they didn't quote the short description global_switch+=("$function_switch") global_descrip+=("$*") } function switch_to_func_name () { printf '%s' "$1" | sed 's/^-//' | sed 's/-/_/g' } #ADDING FUNCTIONS: #the function to call is automatically generated from the command switch (see above functions) #the function MUST follow the same naming convention #you can add names of temporary files before they are created by any command, to ensure that no extra files get left behind on failure #if the temporaries could be large, feel free to both add them to the list, and delete them manually create_function "-border-file-concatenate" "MERGE BORDER FILES" function border_file_concatenate () { local function_switch="$1" shift if (($# < 1)) then echo "`switch_to_descrip $function_switch`" echo " $global_script_name $function_switch" echo " " echo " | -from-file [-big-text] " echo " ..." echo #wrap guide for 80 columns | echo " Additional input files may be specified after the mandatory input file." echo echo " Any input argument can be replaced with a -from-file option followed by a" echo " text file. Each text file must have one data file per line. The" echo " -big-text option allows the use of very large text files (>1MB). This" echo " is otherwise considered an error, in order to prevent accidentally" echo " loading a data file into shell memory as a text file." echo return 0 fi if (($# < 2)) then error "function requires 2 or more arguments" fi local -a merge_arg_array for ((i = 2; i <= $#; ++i)) do if [[ "${!i}" == -* ]] then case "${!i}" in (-from-file) i=$((i + 1)) if ((i > $#)) then error "-from-file option requires an argument" fi local checksize=1 if [[ "${!i}" == -big-text ]] then checksize=0 i=$((i + 1)) fi local filename="${!i}" local nexti=$((i + 1)) #allow the option after the filename, too, because that is the easy place to put it if ((nexti <= $#)) && [[ "${!nexti}" == -big-text ]] then checksize=0 i=$((i + 1)) fi if ((checksize == 1)) then local filesize=`wc -c "$filename" | cut -d' ' -f1` if ((filesize > 1000000)) then error "file '${filename}' seems too large to be a text file" fi fi local -a filecontents readarray -t filecontents < "$filename" for ((j = 0; j < ${#filecontents[@]}; ++j)) do #ignore empty lines if [[ "${filecontents[$j]}" != "" ]] then merge_arg_array+=("-border" "${filecontents[$j]}") fi done ;; (*) error "unrecognized option '${!i}'" ;; esac else merge_arg_array+=("-border" "${!i}") fi done wb_command -border-merge "$1" "${merge_arg_array[@]}" } create_function "-cifti-concatenate" "MERGE MAPS OF CIFTI FILES" function cifti_concatenate () { local function_switch="$1" shift if (($# < 1)) then echo "`switch_to_descrip $function_switch`" echo " $global_script_name $function_switch" echo " [-map ] - use only the specified map from each input" echo " file" echo " " echo " | -from-file [-big-text] " echo " ..." echo #wrap guide for 80 columns | echo " Additional input files may be specified after the mandatory input file." echo echo " Any input argument can be replaced with a -from-file option followed by a" echo " text file. Each text file must have one data file per line. The" echo " -big-text option allows the use of very large text files (>1MB). This" echo " is otherwise considered an error, in order to prevent accidentally" echo " loading a data file into shell memory as a text file." echo echo " The -map option takes either a 1-based" echo " index or a map name, and causes the" echo " operation to use only one map from each input file." echo return 0 fi local -a maparg if [[ "$1" == "-map" ]] then if (($# < 2)) then error "-map option requires an argument" fi maparg=("-column" "$2") shift 2 fi if (($# < 2)) then error "function requires 2 or more arguments" fi local -a merge_arg_array for ((i = 2; i <= $#; ++i)) do if [[ "${!i}" == -* ]] then case "${!i}" in (-from-file) i=$((i + 1)) if ((i > $#)) then error "-from-file option requires an argument" fi local checksize=1 if [[ "${!i}" == -big-text ]] then checksize=0 i=$((i + 1)) fi local filename="${!i}" local nexti=$((i + 1)) #allow the option after the filename, too, because that is the easy place to put it if ((nexti <= $#)) && [[ "${!nexti}" == -big-text ]] then checksize=0 i=$((i + 1)) fi if ((checksize == 1)) then local filesize=`wc -c "$filename" | cut -d' ' -f1` if ((filesize > 1000000)) then error "file '${filename}' seems too large to be a text file" fi fi local -a filecontents readarray -t filecontents < "$filename" for ((j = 0; j < ${#filecontents[@]}; ++j)) do #ignore empty lines if [[ "${filecontents[$j]}" != "" ]] then merge_arg_array+=("-cifti" "${filecontents[$j]}" ${maparg[@]+"${maparg[@]}"}) fi done ;; (*) error "unrecognized option '${!i}'" ;; esac else #we INTENTIONALLY expand an empty array to 0 elements, but set -u doesn't like this, and there is no simple syntax to tell it to allow it #so, use complicated syntax to tell it to allow it #this syntax is apparently UNDOCUMENTED, but it came from stackoverflow, and it works, so it must be right #http://stackoverflow.com/questions/7577052/bash-empty-array-expansion-with-set-u merge_arg_array+=("-cifti" "${!i}" ${maparg[@]+"${maparg[@]}"}) fi done wb_command -cifti-merge "$1" "${merge_arg_array[@]}" } create_function "-cifti-demean" "DEMEAN/NORMALIZE AND CONCATENATE" function cifti_demean () { local function_switch="$1" shift if (($# < 1)) then echo "`switch_to_descrip $function_switch`" echo " $global_script_name $function_switch" echo " [-normalize] - also normalize input files" echo " " echo " " echo " ..." echo #wrap guide for 80 columns | echo " Demeans each input file (optionally normalizes by stdev) and then" echo " concatenates them. Additional input files may be specified after the" echo " mandatory input file." echo return 0 fi local normalize=0 if [[ "$1" == "-normalize" ]] then normalize=1 shift fi if (($# < 2)) then error "function requires 2 or more arguments" fi local tempext=$(echo "$1" | cut -f2- -d.) local -a merge_arg_array for ((i = 2; i <= $#; ++i)) do local tempext2=$(echo "${!i}" | cut -f2- -d.) #we might not make the stdev temporary, but it isn't a problem to add it anyway add_temporary_files "$1.temp${i}-mean-$$.dscalar.nii" "$1.temp${i}-normed-$$.$tempext2" "$1.temp${i}-stdev-$$.dscalar.nii" wb_command -cifti-reduce "${!i}" MEAN "$1.temp${i}-mean-$$.dscalar.nii" if [[ $normalize == 1 ]] then wb_command -cifti-reduce "${!i}" SAMPSTDEV "$1.temp${i}-stdev-$$.dscalar.nii" wb_command -cifti-math '(x - mean) / stdev' "$1.temp${i}-normed-$$.$tempext2" -fixnan 0 \ -var x "${!i}" \ -var mean "$1.temp${i}-mean-$$.dscalar.nii" -select 1 1 -repeat \ -var stdev "$1.temp${i}-stdev-$$.dscalar.nii" -select 1 1 -repeat rm -f "$1.temp${i}-mean-$$.dscalar.nii" "$1.temp${i}-stdev-$$.dscalar.nii" else wb_command -cifti-math 'x - mean' "$1.temp${i}-normed-$$.$tempext2" -fixnan 0 \ -var x "${!i}" \ -var mean "$1.temp${i}-mean-$$.dscalar.nii" -select 1 1 -repeat rm -f "$1.temp${i}-mean-$$.dscalar.nii" fi merge_arg_array+=("-cifti" "$1.temp${i}-normed-$$.$tempext2") done wb_command -cifti-merge "$1" "${merge_arg_array[@]}" #let the exit hook clean up the normed temporaries, we're done } create_function "-cifti-dlabel-split-cortex" "SEPARATE SURFACE LABELS INTO LEFT/RIGHT" function cifti_dlabel_split_cortex () { local function_switch="$1" shift if (($# < 1)) then echo "`switch_to_descrip $function_switch`" echo " $global_script_name $function_switch" echo " - input dlabel file" echo " - output - output dlabel file" echo #wrap guide for 80 columns | echo " For every label represented on left or right cortex, rename it with a" echo " prefix of 'L_' or 'R_', and change the label values as needed to keep" echo " the new names separate." echo return 0 fi if (($# != 2)) then error "function requires 2 arguments, $# provided" fi #to prevent it from also changing label keys in a multi-map dlabel (because gifti doesn't support label tables per-map), #we need to loop through the columns local i num_maps=`wb_command -file-information -only-number-of-maps "$1"` local -a mergeargs for ((i = 1; i <= num_maps; ++i)) do add_temporary_files "$2".temp$i.dlabel.nii "$2".temp$i.L.label.gii "$2".temp$i.R.label.gii "$2".temp$i.nii.gz add_temporary_files "$2".temp$i.L.txt "$2".temp$i.R.txt "$2".temp$i.vol.txt wb_command -cifti-merge "$2".temp$i.dlabel.nii -cifti "$1" -column $i wb_command -cifti-separate "$2".temp$i.dlabel.nii COLUMN -label CORTEX_LEFT "$2".temp$i.L.label.gii -label CORTEX_RIGHT "$2".temp$i.R.label.gii -volume-all "$2".temp$i.nii.gz -crop #these are temporary files and will be deleted regardless, go ahead and overwrite them #LEFT wb_command -gifti-label-add-prefix "$2".temp$i.L.label.gii L_ "$2".temp$i.L.label.gii #need to remove the unused ones from the table so we don't get stupid overlaps wb_command -label-export-table "$2".temp$i.L.label.gii "$2".temp$i.L.txt #mute the warning from loading a label as a metric wb_command -metric-label-import "$2".temp$i.L.label.gii \ "$2".temp$i.L.txt \ "$2".temp$i.L.label.gii \ -drop-unused-labels 2> /dev/null #RIGHT wb_command -gifti-label-add-prefix "$2".temp$i.R.label.gii R_ "$2".temp$i.R.label.gii #need to remove the unused ones from the table so we don't get stupid overlaps wb_command -label-export-table "$2".temp$i.R.label.gii "$2".temp$i.R.txt #mute the warning from loading a label as a metric wb_command -metric-label-import "$2".temp$i.R.label.gii \ "$2".temp$i.R.txt \ "$2".temp$i.R.label.gii \ -drop-unused-labels 2> /dev/null #VOLUME #need to also remove unused labels from this, too wb_command -volume-label-export-table "$2".temp$i.nii.gz 1 "$2".temp$i.vol.txt #no warning to mute wb_command -volume-label-import "$2".temp$i.nii.gz \ "$2".temp$i.vol.txt \ "$2".temp$i.nii.gz \ -drop-unused-labels wb_command -cifti-create-dense-from-template "$2".temp$i.dlabel.nii "$2".temp$i.dlabel.nii -label CORTEX_LEFT "$2".temp$i.L.label.gii -label CORTEX_RIGHT "$2".temp$i.R.label.gii -volume-all "$2".temp$i.nii.gz -from-cropped rm -f "$2".temp$i.L.label.gii "$2".temp$i.R.label.gii "$2".temp$i.nii.gz rm -f "$2".temp$i.L.txt "$2".temp$i.R.txt "$2".temp$i.vol.txt mergeargs+=(-cifti "$2".temp$i.dlabel.nii) done wb_command -cifti-merge "$2" "${mergeargs[@]}" #let the cleanup function remove the intermediate cifti files } create_function "-command-help-search" "SEARCH WB_COMMAND HELP" function command_help_search () { local function_switch="$1" shift if (($# < 1)) then echo "`switch_to_descrip $function_switch`" echo " $global_script_name $function_switch" echo " [-also-deprecated] - also search deprecated commands" echo " - grep pattern to search for" echo #wrap guide for 80 columns | echo " Searches for wb_command processing commands that contain the pattern." echo return 0 fi local -a switches readarray -t switches < <(wb_command -list-commands | awk '{print $1}') if [[ "$1" == "-also-deprecated" ]] then shift local -a depswitches readarray -t depswitches < <(wb_command -list-deprecated-commands | awk '{print $1}') switches+=("${depswitches[@]}") fi #we could add a -- option to denote end of options, but then it would be harder to search for -- rather than for an option if (($# < 1)) then error "function requires an argument" fi #requiring quotes when searching for a phrase is slightly inconvenient #on the other hand, a phrase could get split across lines, so that is error prone anyway #if (($# != 1)) #then # warning "more than one argument provided, check for unquoted spaces and spelling of options" #fi local i for ((i = 0; i < ${#switches[@]}; ++i)) do #use $* in case someone didn't quote, but keep it all in the pattern argument local matches=`wb_command "${switches[$i]}" | grep -i -e "$*"` if [[ "$matches" != "" ]] then printf '%s\n' "${switches[$i]}:" printf '%s\n\n' "$matches" fi done } create_function "-freesurfer-resample-prep" "CREATE MIDTHICKNESSES FROM FREESURFER" function freesurfer_resample_prep () { local function_switch="$1" shift if (($# < 1)) then echo "`switch_to_descrip $function_switch`" echo " $global_script_name $function_switch" echo " - the freesurfer white surface" echo " - the freesurfer pial surface" echo " " echo " " echo " - output - the midthickness on the current" echo " freesurfer mesh, in gifti format" echo " - output - likewise, on the new mesh" echo " - output - the freesurfer sphere converted to" echo " gifti, must end in '.surf.gii'" echo #wrap guide for 80 columns | echo " NOTE: freesurfer's mris_convert must be installed and in the \$PATH in" echo " order to use this function, for converting the surfaces to GIFTI format." echo echo " Generate the various surface files used for resampling individual data" echo " from FreeSurfer to fs_LR. This generates the gifti-format sphere, and" echo " both midthickness surfaces needed by the -area-surfs option of wb_command" echo " -metric-resample, -label-resample, and similar commands." echo return 0 fi if (($# != 7)) then error "function requires 7 arguments, $# provided" fi if [[ "$7" != *.surf.gii ]] then error " filename must end in '.surf.gii'" fi add_temporary_files "$5.temp$$.white.surf.gii" "$5.temp$$.pial.surf.gii" if [[ "$5" == */* ]] then #if output name includes a path (even relative), then we don't need to futz with it to prevent freesurfer from dumping the converted file in the INPUT file's directory mris_convert "$1" "$5.temp$$.white.surf.gii" mris_convert "$2" "$5.temp$$.pial.surf.gii" else #if you give freesurfer a bare filename with no path for output, it dumps it in the input directory, regardless of cwd #so, put ./ on the beginning to tell it to mend its ways mris_convert "$1" ./"$5.temp$$.white.surf.gii" mris_convert "$2" ./"$5.temp$$.pial.surf.gii" fi mris_convert "$3" "$7" wb_command -surface-average "$5" -surf "$5.temp$$.white.surf.gii" -surf "$5.temp$$.pial.surf.gii" rm -f "$5.temp$$.white.surf.gii" "$5.temp$$.pial.surf.gii" wb_command -surface-resample "$5" "$7" "$4" BARYCENTRIC "$6" } create_function "-import-probtrack-roi" "CONVERT ROI TRACKS TO CIFTI" function import_probtrack_roi () { local function_switch="$1" shift if (($# < 1)) then echo "`switch_to_descrip $function_switch`" echo " $global_script_name $function_switch" echo " - the text file from probtrackx2" echo " - the ROI used as the seed mask, as cifti" echo " - output - the data converted to cifti dscalar" echo #wrap guide for 80 columns | echo " The file should contain the ROI used as the mask, and should" echo " be in the desired brainordinate space." echo return 0 fi if (($# != 3)) then error "function requires 3 arguments, $# provided" fi local tempext=$(echo "$2" | cut -f2- -d.) #make temporaries based on the output file name add_temporary_files "$3.temp1-$$.$tempext" "$3.temp2-$$.dscalar.nii" wb_command -cifti-restrict-dense-map "$2" COLUMN "$3.temp1-$$.$tempext" -cifti-roi "$2" wb_command -cifti-convert -from-text "$1" "$3.temp1-$$.$tempext" "$3.temp2-$$.dscalar.nii" -reset-scalars rm -f "$3.temp1-$$.$tempext" wb_command -cifti-create-dense-from-template "$2" "$3" -cifti "$3.temp2-$$.dscalar.nii" rm -f "$3.temp2-$$.dscalar.nii" } create_function "-label-concatenate" "MERGE MAPS OF LABEL FILES" function label_concatenate () { local function_switch="$1" shift if (($# < 1)) then echo "`switch_to_descrip $function_switch`" echo " $global_script_name $function_switch" echo " [-map ] - use only the specified map from each input" echo " file" echo " " echo " | -from-file [-big-text] " echo " ..." echo #wrap guide for 80 columns | echo " Additional input files may be specified after the mandatory input file." echo echo " Any input argument can be replaced with a -from-file option followed by a" echo " text file. Each text file must have one data file per line. The" echo " -big-text option allows the use of very large text files (>1MB). This" echo " is otherwise considered an error, in order to prevent accidentally" echo " loading a data file into shell memory as a text file." echo echo " The -map option takes either a 1-based" echo " index or a map name, and causes the" echo " operation to use only one map from each input file." echo return 0 fi local -a maparg if [[ "$1" == "-map" ]] then if (($# < 2)) then error "-map option requires an argument" fi maparg=("-column" "$2") shift 2 fi if (($# < 2)) then error "function requires 2 or more arguments" fi local -a merge_arg_array local i for ((i = 2; i <= $#; ++i)) do if [[ "${!i}" == -* ]] then case "${!i}" in (-from-file) i=$((i + 1)) if ((i > $#)) then error "-from-file option requires an argument" fi local checksize=1 if [[ "${!i}" == -big-text ]] then checksize=0 i=$((i + 1)) fi local filename="${!i}" local nexti=$((i + 1)) #allow the option after the filename, too, because that is the easy place to put it if ((nexti <= $#)) && [[ "${!nexti}" == -big-text ]] then checksize=0 i=$((i + 1)) fi if ((checksize == 1)) then local filesize=`wc -c "$filename" | cut -d' ' -f1` if ((filesize > 1000000)) then error "file '${filename}' seems too large to be a text file" fi fi local -a filecontents readarray -t filecontents < "$filename" for ((j = 0; j < ${#filecontents[@]}; ++j)) do #ignore empty lines if [[ "${filecontents[$j]}" != "" ]] then merge_arg_array+=("-label" "${filecontents[$j]}" ${maparg[@]+"${maparg[@]}"}) fi done ;; (*) error "unrecognized option '${!i}'" ;; esac else merge_arg_array+=("-label" "${!i}" ${maparg[@]+"${maparg[@]}"}) fi done wb_command -label-merge "$1" "${merge_arg_array[@]}" } create_function "-metric-concatenate" "MERGE MAPS OF METRIC FILES" function metric_concatenate () { local function_switch="$1" shift if (($# < 1)) then echo "`switch_to_descrip $function_switch`" echo " $global_script_name $function_switch" echo " [-map ] - use only the specified map from each input" echo " file" echo " " echo " | -from-file [-big-text] " echo " ..." echo #wrap guide for 80 columns | echo " Additional input files may be specified after the mandatory input file." echo echo " Any input argument can be replaced with a -from-file option followed by a" echo " text file. Each text file must have one data file per line. The" echo " -big-text option allows the use of very large text files (>1MB). This" echo " is otherwise considered an error, in order to prevent accidentally" echo " loading a data file into shell memory as a text file." echo echo " The -map option takes either a 1-based" echo " index or a map name, and causes the" echo " operation to use only one map from each input file." echo return 0 fi local -a maparg if [[ "$1" == "-map" ]] then if (($# < 2)) then error "-map option requires an argument" fi maparg=("-column" "$2") shift 2 fi if (($# < 2)) then error "function requires 2 or more arguments" fi local -a merge_arg_array local i for ((i = 2; i <= $#; ++i)) do if [[ "${!i}" == -* ]] then case "${!i}" in (-from-file) i=$((i + 1)) if ((i > $#)) then error "-from-file option requires an argument" fi local checksize=1 if [[ "${!i}" == -big-text ]] then checksize=0 i=$((i + 1)) fi local filename="${!i}" local nexti=$((i + 1)) #allow the option after the filename, too, because that is the easy place to put it if ((nexti <= $#)) && [[ "${!nexti}" == -big-text ]] then checksize=0 i=$((i + 1)) fi if ((checksize == 1)) then local filesize=`wc -c "$filename" | cut -d' ' -f1` if ((filesize > 1000000)) then error "file '${filename}' seems too large to be a text file" fi fi local -a filecontents readarray -t filecontents < "$filename" for ((j = 0; j < ${#filecontents[@]}; ++j)) do #ignore empty lines if [[ "${filecontents[$j]}" != "" ]] then merge_arg_array+=("-metric" "${filecontents[$j]}" ${maparg[@]+"${maparg[@]}"}) fi done ;; (*) error "unrecognized option '${!i}'" ;; esac else merge_arg_array+=("-metric" "${!i}" ${maparg[@]+"${maparg[@]}"}) fi done wb_command -metric-merge "$1" "${merge_arg_array[@]}" } create_function "-scene-add-files" "ADD FILES TO BE LOADED IN A SCENE" function scene_add_files () { local function_switch="$1" shift if (($# < 1)) then echo "`switch_to_descrip $function_switch`" echo " $global_script_name $function_switch" echo " " echo " " echo " " echo " " echo " ..." echo #wrap guide for 80 columns | echo " Additional data files may be specified after the mandatory data file." echo " Data files are passed as -data-file-add arguments to -scene-file-update," echo " so text files (with the extension .txt) containing a list of data files" echo " are supported." echo return 0 fi if (($# < 4)) then error "function requires 4 or more arguments" fi local input="$1" local sceneid="$2" local output="$3" shift 3 local -a fileargs=() for file in "$@" do fileargs+=(-data-file-add "$file") done #WARNING: this command doesn't have the output file last among the mandatory arguments wb_command -scene-file-update \ "$input" \ "$output" \ "$sceneid" \ "${fileargs[@]}" } create_function "-scene-file-concatenate" "MERGE SCENES OF SCENE FILES" function scene_file_concatenate () { local function_switch="$1" shift if (($# < 1)) then echo "`switch_to_descrip $function_switch`" echo " $global_script_name $function_switch" echo " " echo " | -from-file [-big-text] " echo " ..." echo #wrap guide for 80 columns | echo " Additional input files may be specified after the mandatory input file." echo echo " Any input argument can be replaced with a -from-file option followed by a" echo " text file. Each text file must have one data file per line. The" echo " -big-text option allows the use of very large text files (>1MB). This" echo " is otherwise considered an error, in order to prevent accidentally" echo " loading a data file into shell memory as a text file." echo return 0 fi if (($# < 2)) then error "function requires 2 or more arguments" fi local -a merge_arg_array local i for ((i = 2; i <= $#; ++i)) do if [[ "${!i}" == -* ]] then case "${!i}" in (-from-file) i=$((i + 1)) if ((i > $#)) then error "-from-file option requires an argument" fi local checksize=1 if [[ "${!i}" == -big-text ]] then checksize=0 i=$((i + 1)) fi local filename="${!i}" local nexti=$((i + 1)) #allow the option after the filename, too, because that is the easy place to put it if ((nexti <= $#)) && [[ "${!nexti}" == -big-text ]] then checksize=0 i=$((i + 1)) fi if ((checksize == 1)) then local filesize=`wc -c "$filename" | cut -d' ' -f1` if ((filesize > 1000000)) then error "file '${filename}' seems too large to be a text file" fi fi local -a filecontents readarray -t filecontents < "$filename" for ((j = 0; j < ${#filecontents[@]}; ++j)) do #ignore empty lines if [[ "${filecontents[$j]}" != "" ]] then merge_arg_array+=("-scene-file" "${filecontents[$j]}") fi done ;; (*) error "unrecognized option '${!i}'" ;; esac else merge_arg_array+=("-scene-file" "${!i}") fi done wb_command -scene-file-merge "$1" "${merge_arg_array[@]}" } create_function "-volume-concatenate" "MERGE MAPS OF VOLUME FILES" function volume_concatenate () { local function_switch="$1" shift if (($# < 1)) then echo "`switch_to_descrip $function_switch`" echo " $global_script_name $function_switch" echo " [-map ] - use only the specified map from each input" echo " file" echo " " echo " | -from-file [-big-text] " echo " ..." echo #wrap guide for 80 columns | echo " Additional input files may be specified after the mandatory input file." echo echo " Any input argument can be replaced with a -from-file option followed by a" echo " text file. Each text file must have one data file per line. The" echo " -big-text option allows the use of very large text files (>1MB). This" echo " is otherwise considered an error, in order to prevent accidentally" echo " loading a data file into shell memory as a text file." echo echo " The -map option takes either a 1-based" echo " index or a map name, and causes the" echo " operation to use only one map from each input file." echo return 0 fi local -a maparg if [[ "$1" == "-map" ]] then if (($# < 2)) then error "-map option requires an argument" fi maparg=("-subvolume" "$2") shift 2 fi if (($# < 2)) then error "function requires 2 or more arguments" fi local -a merge_arg_array local i for ((i = 2; i <= $#; ++i)) do if [[ "${!i}" == -* ]] then case "${!i}" in (-from-file) i=$((i + 1)) if ((i > $#)) then error "-from-file option requires an argument" fi local checksize=1 if [[ "${!i}" == -big-text ]] then checksize=0 i=$((i + 1)) fi local filename="${!i}" local nexti=$((i + 1)) #allow the option after the filename, too, because that is the easy place to put it if ((nexti <= $#)) && [[ "${!nexti}" == -big-text ]] then checksize=0 i=$((i + 1)) fi if ((checksize == 1)) then local filesize=`wc -c "$filename" | cut -d' ' -f1` if ((filesize > 1000000)) then error "file '${filename}' seems too large to be a text file" fi fi local -a filecontents readarray -t filecontents < "$filename" for ((j = 0; j < ${#filecontents[@]}; ++j)) do #ignore empty lines if [[ "${filecontents[$j]}" != "" ]] then merge_arg_array+=("-volume" "${filecontents[$j]}" ${maparg[@]+"${maparg[@]}"}) fi done ;; (*) error "unrecognized option '${!i}'" ;; esac else merge_arg_array+=("-volume" "${!i}" ${maparg[@]+"${maparg[@]}"}) fi done wb_command -volume-merge "$1" "${merge_arg_array[@]}" } #additional helper functions function do_operation () { #use the existence of a short description to denote existence of the function local i for ((i = 0; i < ${#global_switch[@]}; ++i)) do if [[ "${global_switch[$i]}" == "$1" ]] then `switch_to_func_name "$1"` "$@" return $? fi done local maxlength=0 for ((i = 0; i < ${#global_switch[@]}; ++i)) do if [[ "${global_switch[$i]}" == "$1"* ]] then local thislength=`printf '%s' "${global_switch[$i]}" | wc -c` if (( thislength > maxlength )) then maxlength=$thislength fi fi done if (( maxlength == 0 )) then error "the switch '$1' does not match any functions" fi for ((i = 0; i < ${#global_switch[@]}; ++i)) do if [[ "${global_switch[$i]}" == "$1"* ]] then printf "%-${maxlength}s %s\n" "${global_switch[$i]}" "${global_descrip[$i]}" fi done | sort } function switch_to_descrip () { #because we aren't using associative arrays, because mac's ancient bash local i for ((i = 0; i < ${#global_switch[@]}; ++i)) do if [[ "${global_switch[$i]}" == "$1" ]] then printf '%s' "${global_descrip[$i]}" return 0 fi done return 1 } function list_functions () { local i maxlength=0 for ((i = 0; i < ${#global_switch[@]}; ++i)) do local thislength=`printf '%s' "${global_switch[$i]}" | wc -c` if (( thislength > maxlength )) then maxlength=$thislength fi done for ((i = 0; i < ${#global_switch[@]}; ++i)) do printf "%-${maxlength}s %s\n" "${global_switch[$i]}" "${global_descrip[$i]}" done | sort } function all_functions_help () { #assume that the commands all print their help info when given no additional arguments besides the command switch itself local i for ((i = 0; i < ${#global_switch[@]}; ++i)) do `switch_to_func_name "${global_switch[$i]}"` "${global_switch[$i]}" done } function error () { echo >&2 echo "While running:" echo "$global_cmd_line" >&2 echo >&2 echo "Error: $*" >&2 echo >&2 exit 1 } function warning () { echo >&2 echo "Warning: $*" >&2 echo >&2 } function check_functions () { local i for ((i = 0; i < ${#global_switch[@]}; ++i)) do local function_name=`switch_to_func_name "${global_switch[$i]}"` if [[ `type -t "$function_name"` != 'function' ]] then echo "ASSERT FAILURE: switch '${global_switch[$i]}' does not have matching function '$function_name'" exit 1 fi done } global_temporary_files=() function add_temporary_files () { global_temporary_files+=("$@") } function cleanup () { #unset variable gets tripped by @ expansion on empty array #so first test if the array is empty if ((${#global_temporary_files[@]} > 0)) then rm -f -- "${global_temporary_files[@]}" &> /dev/null fi } trap cleanup EXIT #start the main code, now that all the functions are defined main "$@"