[BASH] Getopts/shift within a function, unexpected behaviour

Hello Gurus :slight_smile:

I'm "currently" (for the last ~2weeks) writing a script to build ffmpeg with some features from scratch.
This said, there are quite a few features, libs, to be downloaded, compiled and installed, so figured, writing functions for some default tasks might help.
Specialy since this is quite a task to achieve, one want to have it reproducable, right?

Also, i wanted to have seperate log files for each lib, instead of everything on the screen.
So i've put that handling into those functions.

Anyway, the issue is, that on some occasions the functions get 2 args, but shifts one eventhough it should not. (to my understand at least)

So my main question is:
Why does the first variable of the 'faac default-download' become deleted/shifted?

Code segment:
The first blocks are just to compare.

#
# 	Audio
#
	tui-title "Download & compiling dependencies.."
	
	# http://www.linuxfromscratch.org/blfs/view/svn/multimedia/lame.html
	default_download libmp3lame "$URL_libmp3lame"
	default_build    libmp3lame
	tui-bgjob "$TMP" "Building libmp3lame..." "Built libmp3lame."
	
	# http://www.linuxfromscratch.org/blfs/view/svn/multimedia/libogg.html
	default_download libogg "$URL_libogg"
	default_build  libogg
	tui-bgjob "$TMP" "Building libogg..." "Built libogg."
	
	# http://www.linuxfromscratch.org/blfs/view/svn/multimedia/opus.html
#	default_git libopus "$GIT_libopus"
#	autoreconf -fiv  1>>"$LOG_DIR/libopus.log" 2>>"$LOG_DIR/libopus.log"
#	default_build -c libopus
#	tui-bgjob "$TMP" "Building libopus..." "Built libopus."
	
	# http://www.linuxfromscratch.org/blfs/view/svn/multimedia/fdk-aac.html
	default_git libfdk_aac "$GIT_libfdk_aac"
	./autogen.sh 1>>"$LOG_DIR/libfdk_aac.log" 2>>"$LOG_DIR/libfdk_aac.log"
	default_build -c libfdk_aac
	tui-bgjob "$TMP" "Building libfdk_aac..." "Built libfdk_aac."
	
	# http://www.linuxfromscratch.org/blfs/view/svn/multimedia/faac.html
	set -x
	default_download faac "$URL_faac"
	default_build -c faac 
	tui-bgjob "$TMP" "Building faac..." "Built faac."
	

Functions (related):

	function default_download() { # [-p PATH -f FILENAME] NAME URL
	# -f NAME, renames basename of url to name
	# -p PATH, changes to this path rathen than to tarball without extension
		URL="" ; PFAD="" ; TARBALL="" ; DATEI="" ; doPfad=false ; doFile=false
		while getopts "p:f:" opt
		do	case "$opt" in
			p)	PFAD="$OPTARG"	# german for PATH, so i dont overwrite that global env var.
				doPfad=true		;;
			f)	TARBALL="$OPTARG"
				doFile=true		;;
			esac
		done
echo "$# $*"
		shift $(( $OPTIND - 1 ))
echo "$# $*"
		[ -z "$1" ] && \
			tui-printf -S 1 "Error, no name passed!" && \
			return 1
		[ -z "$2" ] && \
			tui-printf -S 1 "Error, no URL found..." && \
			return 1
		LOG_THIS="$LOG_DIR/$1.log"
		URL="$2"
		[ -z "$TARBALL" ] && \
			TARBALL="${URL##*/}"
		[ -z "$PFAD" ] && \
			PFAD="${TARBALL/.tar*/}"
		
		if [ -f "$TARBALL" ] || [ -d "$1" ] || [ -d "$PFAD" ]
		then	doLog "$1: Skiping download"
			tui-status 4 "Skiping download"
		else	doLog "$1: Downloading from $URL"
			tui-download "$URL"
			$doFile && tui-mv "${URL##*/}" "$TARBALL"
			doLog "$1: Downloaded to $TARBALL"
			tui-tar -x "$TARBALL"
			doLog "$1: Extracted $TARBALL"
		fi
		cd "$PFAD"
		doLog "$1: Entering ./$PFAD"
	}
	function default_build() { # PKG_STRING ["CONFIG_OPTS"]
	# Builds a package with default behaviour
	# -c, makes distclean
		distclean=false
		while getopts "c" opt
		do	case "$opt" in
			c)	distclean=true		;;
			esac
		done
		shift $(( $OPTIND - 1 ))
		this="$1" ; shift
		LOG_THIS="$LOG_DIR/$this.log"
		[ $# -gt 0 ] && \
			doLog "$this: ./configure .. + ${@}" 
		
		cat > "$TMP" <<-EOF
		PKG_CONFIG_PATH=$PKG_CONFIG_PATH \\
		 	./configure \\
		 		--prefix="$PREFIX" --bindir=$DIR_BIN --libdir=$DIR_LIB --includedir=$DIR_INC --sysconfdir=$CHROOT/etc \\
		 		--disable-static --enable-shared  ${@} 2>>"$LOG_THIS" 1>>"$LOG_THIS"
		
		doLog "$1: Starting make..."
		make V=1			2>>"$LOG_THIS" 1>>"$LOG_THIS"
		doLog "$1: make exit with \$?"
		
		sudo make install		2>>"$LOG_THIS" 1>>"$LOG_THIS"
		RET=\$?
		doLog "$1: make install exit with \$RET"
		
		$distclean && make distclean	2>>"$LOG_THIS" 1>>"$LOG_THIS"
		cd "$DIR_SRC"
		>> "$LOG"
		exit \$RET
		EOF
	}
	export -f doLog
	export -f default_download
	export -f default_git
	export -f default_build

Outputs:

# | Sea's custom ffmpeg build (0.8)                                                          2015-04-17 16:25:33 | #
# | Found: /home/sea/net/dls/ffmpeg_sources                                                             [     ] | #
# | Found: /home/sea/.local                                                                             [     ] | #
# | Found: /home/sea/.local/logs                                                                        [     ] | #
# |                                     Download & compiling dependencies..                                      | #
2 libmp3lame http://downloads.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz
2 libmp3lame http://downloads.sourceforge.net/project/lame/lame/3.99/lame-3.99.5.tar.gz
# | Skiping download                                                                                    [  �   ] | #
# | Built libmp3lame.                                                                                   [     ] | #
2 libogg http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.gz
2 libogg http://downloads.xiph.org/releases/ogg/libogg-1.3.2.tar.gz
# | Skiping download                                                                                    [  �   ] | #
# | Built libogg.                                                                                       [     ] | #
# | Built libfdk_aac.                                                                                   [     ] | #
+ default_download faac http://downloads.sourceforge.net/faac/faac-1.28.tar.bz2
+ URL=
+ PFAD=
+ TARBALL=
+ DATEI=
+ doPfad=false
+ doFile=false
+ getopts p:f: opt
+ echo '2 faac http://downloads.sourceforge.net/faac/faac-1.28.tar.bz2'
2 faac http://downloads.sourceforge.net/faac/faac-1.28.tar.bz2
+ shift 1
+ echo '1 http://downloads.sourceforge.net/faac/faac-1.28.tar.bz2'
1 http://downloads.sourceforge.net/faac/faac-1.28.tar.bz2
+ '[' -z http://downloads.sourceforge.net/faac/faac-1.28.tar.bz2 ']'
+ '[' -z '' ']'
+ tui-printf -S 1 'Error, no URL found...'
# | Error, no URL found...                                                                              [     ] | #
+ return 1
+ default_build -c faac
+ distclean=false
+ getopts c opt
+ shift 1
+ this=faac
+ shift
+ LOG_THIS=/home/sea/.local/logs/faac.log
+ '[' 0 -gt 0 ']'
+ cat
+ tui-bgjob /home/sea/.local/logs/command.sh 'Building faac...' 'Built faac.'
# | Building faac...    

Obviously aborted the build, since it will fail with the URL as (temp-)file name and missing the very same URL mainwhile...

Any ideas please?
Thank you in advance!

Don't use OPTIND unless getopt actually finds anything. I bet you're using the OPTIND from a previous function call, unchanged.

1 Like

So you are saying, i should first catch the total number of arguments passed, and only if it exceeds 2 use OPTIND?
Doesnt it get 'refilled'/'reset' at each function call (or while getopts ...) anyway?

I now have:

[ $# -gt 2 ] && shift $(( $OPTIND - 1 ))

Seems to fix the issue.

But in NO script i have the shift OPTIND with a condition combined, why do i need when its within a function?
I just dont get it.

Thank you

getopts sets OPTIND when it finds an argument. What happens when it finds no arguments? It doesn't change OPTIND at all. And you are using it for multiple sets of arguments...

                OPTIND=5 # garbage value leftover from things done earlier

                while getopts "p:f:" opt
		do	case "$opt" in
			p)	PFAD="$OPTARG"	# german for PATH, so i dont overwrite that global env var.
				doPfad=true		;;
			f)	TARBALL="$OPTARG"
				doFile=true		;;
			esac
		done

                shift $(( $OPTIND - 1 ))

...which means that 5, or whatever it happened to be, slips straight through the loop and eats some of your arguments.

Probably the best way to fix it would be to set OPTIND=1 at the top of your functions.

1 Like