UPD. 2010.09.07
If we need to return several values from function, we can return through - either setting some global variables, - or through pipe ("stdout"). - or use both. 1. Global variables. If we do not want to hardcode these global variable names into script, we can pass their names as arguments and then use `eval` to assign values in child function, f() { .. eval "$1=\"a b\"" } f g1 declare -p g1 but in this case there is possible name conflicts between local function's variables and global ones. If local variable has the same name as global, it replaces it and we no longer able to set global variable in this child function. 2. Pipe. If we use pipe, we'll first gather all values from local child function's variables and send them into pipe, but then, in caller function, we'll parse pipe content to separate these values again and assign to proper variables. This is waste. Also, order, in which results will be outputed into pipe, should be fixed since cutting of result into separate values again and assigning them to proper variables into caller function assumes certain order. And if this order sometimes accidently will change, returned values will be messed. f3() { local l1='a b' local l2=' c d' echo "\"$l1\"" echo "\"$l2\"" return 0 } eval "$(echo "$(f3)" | sed -e'1s/^/g1=/;2s/^/g2=/')" declare -p g1 g2 3. Mixed. But we can use both those techniques at the same time to eliminate all listed above problems each of them have. Names of global variables to be set should be passed through arguments, but in the pipe we'll send not raw values, but assignment statements with already expanded values, and in caller function we should simply execute them (assignment statements) in `eval`. In such case we get: - no name conflicts, since we're no longer need global variables into child function: we need only their names to generate proper output, but values to them will be assigned into `eval` in caller function. - no parsing of pipe by caller, since child function knows names of variables to which caller want to assign values, and send the output with already prepared assignment statements (values in this assignment statements must be expanded in child function, since child's local variables will be deleted upon it returns). - Order of variables in child function's output may be any (except, very special cases, where one variable use the value of previous ones), since all assignments already written and no parsing required. f() { local l1='local 1' local l2='local 2' echo "g1=\"$l1\"" echo "g2=\"$l2\"" return 0 } declare g1='' declare g2='' eval "$(f g1 g2)" declare -p g1 g2 Note1: - when function is called through command substitution, then it'll be executed in the subshell (unlike, when it's called normally, it is executed in the main shell), so you can _not_ set any parent shell's variable inside it. Thus, all parent shell's variable, which should be set must be written into pipe in assignment statements form. - you can not reinitialize array in parent shell using 'arr=( ${arr[@]} )' syntax, when writing to pipe, if array elements contain shell 'metacharacters' (not IFS characters!). This is because in `eval`-ed script all values will be already expanded (yet before it'll be written into pipe) and will not contain any quotes, so they'll be broken into tokens (words and operators) by shell 'metacharacters' far before any expansions (including word splitting by 'IFS' characters) will take place. - the only way to reinitialize array in parent shell is to write either 'arr[index]=value' for each element or `declare -p arr` into pipe. Here is example. # ./t.sh {{{ #!/bin/bash f() { echo "SUBSH=$BASH_SUBSHELL" >/dev/tty arr1[3]=' g h ' arr2[3]='i k l' IFS=" " echo "arr1=( ${arr1[*]} )" echo "$(declare -p arr2)" return 0 } declare -a arr1=( 'a b' 'c ' ) declare -a arr2=( ' d' ' e f ' ) eval "$(f)" declare -p arr1 arr2 # }}} Note2: Even if we don't need any results from function, which write result into pipe, following considerations should be looked at: - such function should always be invoked in subshell, even if we don't need its results. Otherwise, it'll be executed in the same shell as parent function and its actions with FDs may result in unexpected FD table state for parent. - FD=1 of such function should always be connected to something like '/dev/null'. Otherwise, it can write results somewhere you don't expect them for. Example-1. Here both functions f() and f2() return result through pipe, but function f() does not need what f2() returns, so f2() is called (before f() moves pipe) without subshell and without pipe connected. This results in the following problems: - f2() closes 'fd_t_stdout' in the parent function's FD table, though f() thinks it's still open. This resluts in "9: Bad file descriptor error", when f() tries to move pipe and f() loses correct value for stdout. Hence, f() writes into pipe both output intended for 'stdout' and intended for 'pipe'. - f2() writes its output intended for 'pipe' into the _same_ pipe, as f() uses, because it was invoked before f() moves its own pipe. # cat ./t.sh {{{ #!/bin/bash declare -r -i fd_t_stdout=9 declare -r -i fd_t_pipe=7 declare -r log_file='./2.tmp' declare -r read_file='/dev/zero' [ -f "$log_file" ] && rm -f "$log_file" exec 2>>"$log_file" <"$read_file" f2() { echo "f2(): start: \$\$: $$, BASH=$BASHPID, SUBSH=$BASH_SUBSHELL" >>"$log_file" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f2(): move pipe" >>"$log_file" eval "exec $fd_t_pipe>&1 1>&$fd_t_stdout-" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f2(): write to stdout" echo "f2(): restore pipe" >>"$log_file" exec 1>&$fd_t_pipe- lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f2(): write to pipe" } f() { echo "f(): start: \$\$: $$, BASH=$BASHPID, SUBSH=$BASH_SUBSHELL" >>"$log_file" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 f2 echo "f(): move pipe" >>"$log_file" eval "exec $fd_t_pipe>&1 1>&$fd_t_stdout" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f(): write to stdout" echo "f(): restore pipe" >>"$log_file" eval "exec 1>&$fd_t_pipe-" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f(): write to pipe" } eval "exec $fd_t_stdout>&1" v="$(f)" echo "v: '$v'" eval "exec $fd_t_stdout>&-" # }}} # ./t.sh >|./1.tmp {{{ f(): start: $$: 12681, BASH=12683, SUBSH=1 COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12683 root 0r CHR 1,5 920 /dev/zero t.sh 12683 root 1w FIFO 0,6 31967 pipe t.sh 12683 root 2w REG 8,7 43 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12683 root 9w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp f2(): start: $$: 12681, BASH=12683, SUBSH=1 COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12683 root 0r CHR 1,5 920 /dev/zero t.sh 12683 root 1w FIFO 0,6 31967 pipe t.sh 12683 root 2w REG 8,7 438 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12683 root 9w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp f2(): move pipe COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12683 root 0r CHR 1,5 920 /dev/zero t.sh 12683 root 1w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp t.sh 12683 root 2w REG 8,7 805 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12683 root 7w FIFO 0,6 31967 pipe f2(): restore pipe COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12683 root 0r CHR 1,5 920 /dev/zero t.sh 12683 root 1w FIFO 0,6 31967 pipe t.sh 12683 root 2w REG 8,7 1175 1590776 /home/sgf/new_tree/src/send_sms/2.tmp f(): move pipe ./t.sh: line 36: 9: Bad file descriptor COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12683 root 0r CHR 1,5 920 /dev/zero t.sh 12683 root 1w FIFO 0,6 31967 pipe t.sh 12683 root 2w REG 8,7 1492 1590776 /home/sgf/new_tree/src/send_sms/2.tmp f(): restore pipe ./t.sh: line 43: 7: Bad file descriptor COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12683 root 0r CHR 1,5 920 /dev/zero t.sh 12683 root 1w FIFO 0,6 31967 pipe t.sh 12683 root 2w REG 8,7 1812 1590776 /home/sgf/new_tree/src/send_sms/2.tmp # }}} # cat ./1.tmp {{{ f2(): write to stdout v: 'f2(): write to pipe f(): write to stdout f(): write to pipe' # }}} Example-2. If f() moves its pipe before calling f2(), but calls it also without subshell, this will not be better: - f2() will write its output intended for 'pipe' into 'stdout', since stdout instead of pipe will be opened at FD=1, when f2() starts. - f2() replaces FD='fd_t_pipe' value into parent function's FD table, so saved f()'s pipe will be replaced with 'stdout' (since 'stdout' will be at FD=1 in f2()). So, f() loses correct value for its pipe and writes into 'stdout' both output intended for pipe and intended for 'stdout'. # cat ./t.sh {{{ #!/bin/bash declare -r -i fd_t_stdout=9 declare -r -i fd_t_pipe=7 declare -r log_file='./2.tmp' declare -r read_file='/dev/zero' [ -f "$log_file" ] && rm -f "$log_file" exec 2>>"$log_file" <"$read_file" f2() { echo "f2(): start: \$\$: $$, BASH=$BASHPID, SUBSH=$BASH_SUBSHELL" >>"$log_file" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f2(): move pipe" >>"$log_file" eval "exec $fd_t_pipe>&1 1>&$fd_t_stdout-" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f2(): write to stdout" echo "f2(): restore pipe" >>"$log_file" exec 1>&$fd_t_pipe- lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f2(): write to pipe" } f() { echo "f(): start: \$\$: $$, BASH=$BASHPID, SUBSH=$BASH_SUBSHELL" >>"$log_file" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f(): move pipe" >>"$log_file" eval "exec $fd_t_pipe>&1 1>&$fd_t_stdout" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 f2 echo "f(): write to stdout" echo "f(): restore pipe" >>"$log_file" eval "exec 1>&$fd_t_pipe-" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f(): write to pipe" } eval "exec $fd_t_stdout>&1" v="$(f)" echo "v: '$v'" eval "exec $fd_t_stdout>&-" # }}} # ./t.sh >|./1.tmp {{{ f(): start: $$: 12713, BASH=12715, SUBSH=1 COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12715 root 0r CHR 1,5 920 /dev/zero t.sh 12715 root 1w FIFO 0,6 32160 pipe t.sh 12715 root 2w REG 8,7 43 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12715 root 9w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp f(): move pipe COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12715 root 0r CHR 1,5 920 /dev/zero t.sh 12715 root 1w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp t.sh 12715 root 2w REG 8,7 409 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12715 root 7w FIFO 0,6 32160 pipe t.sh 12715 root 9w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp f2(): start: $$: 12713, BASH=12715, SUBSH=1 COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12715 root 0r CHR 1,5 920 /dev/zero t.sh 12715 root 1w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp t.sh 12715 root 2w REG 8,7 893 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12715 root 7w FIFO 0,6 32160 pipe t.sh 12715 root 9w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp f2(): move pipe COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12715 root 0r CHR 1,5 920 /dev/zero t.sh 12715 root 1w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp t.sh 12715 root 2w REG 8,7 1349 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12715 root 7w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp f2(): restore pipe COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12715 root 0r CHR 1,5 920 /dev/zero t.sh 12715 root 1w REG 8,7 22 1121662 /home/sgf/new_tree/src/send_sms/1.tmp t.sh 12715 root 2w REG 8,7 1752 1590776 /home/sgf/new_tree/src/send_sms/2.tmp f(): restore pipe ./t.sh: line 43: 7: Bad file descriptor COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12715 root 0r CHR 1,5 920 /dev/zero t.sh 12715 root 1w REG 8,7 63 1121662 /home/sgf/new_tree/src/send_sms/1.tmp t.sh 12715 root 2w REG 8,7 2105 1590776 /home/sgf/new_tree/src/send_sms/2.tmp # }}} # cat ./1.tmp {{{ f2(): write to stdout f2(): write to pipe f(): write to stdout f(): write to pipe v: '' # }}} Example-3. And here is how this can be done correctly. Shortly, we should call f2() like ( f2 >/dev/null ) In this case, f2() result will be discarded (but we do not need it, right?), but f() will output its result as expected. # cat ./t.sh {{{ #!/bin/bash declare -r -i fd_t_stdout=9 declare -r -i fd_t_pipe=7 declare -r log_file='./2.tmp' declare -r read_file='/dev/zero' [ -f "$log_file" ] && rm -f "$log_file" exec 2>>"$log_file" <"$read_file" f2() { echo "f2(): start: \$\$: $$, BASH=$BASHPID, SUBSH=$BASH_SUBSHELL" >>"$log_file" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f2(): move pipe" >>"$log_file" eval "exec $fd_t_pipe>&1 1>&$fd_t_stdout-" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f2(): write to stdout" echo "f2(): restore pipe" >>"$log_file" exec 1>&$fd_t_pipe- lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f2(): write to pipe" } f() { echo "f(): start: \$\$: $$, BASH=$BASHPID, SUBSH=$BASH_SUBSHELL" >>"$log_file" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f(): move pipe" >>"$log_file" eval "exec $fd_t_pipe>&1 1>&$fd_t_stdout" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 ( f2 >/dev/null ) echo "f(): write to stdout" echo "f(): restore pipe" >>"$log_file" eval "exec 1>&$fd_t_pipe-" lsof -a -p $BASHPID -d'^mem,^txt,^rtd,^cwd' >>"$log_file" read -rsn1 echo "f(): write to pipe" } eval "exec $fd_t_stdout>&1" v="$(f)" echo "v: '$v'" eval "exec $fd_t_stdout>&-" # }}} # ./t.sh >|./1.tmp {{{ f(): start: $$: 12777, BASH=12779, SUBSH=1 COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12779 root 0r CHR 1,5 920 /dev/zero t.sh 12779 root 1w FIFO 0,6 32627 pipe t.sh 12779 root 2w REG 8,7 43 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12779 root 9w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp f(): move pipe COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12779 root 0r CHR 1,5 920 /dev/zero t.sh 12779 root 1w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp t.sh 12779 root 2w REG 8,7 409 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12779 root 7w FIFO 0,6 32627 pipe t.sh 12779 root 9w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp f2(): start: $$: 12777, BASH=12784, SUBSH=2 COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12784 root 0r CHR 1,5 920 /dev/zero t.sh 12784 root 1w CHR 1,3 898 /dev/null t.sh 12784 root 2w REG 8,7 893 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12784 root 7w FIFO 0,6 32627 pipe t.sh 12784 root 9w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp t.sh 12784 root 10w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp f2(): move pipe COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12784 root 0r CHR 1,5 920 /dev/zero t.sh 12784 root 1w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp t.sh 12784 root 2w REG 8,7 1410 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12784 root 7w CHR 1,3 898 /dev/null t.sh 12784 root 10w REG 8,7 0 1121662 /home/sgf/new_tree/src/send_sms/1.tmp f2(): restore pipe COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12784 root 0r CHR 1,5 920 /dev/zero t.sh 12784 root 1w CHR 1,3 898 /dev/null t.sh 12784 root 2w REG 8,7 1874 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12784 root 10w REG 8,7 22 1121662 /home/sgf/new_tree/src/send_sms/1.tmp f(): restore pipe COMMAND PID USER FD TYPE DEVICE SIZE NODE NAME t.sh 12779 root 0r CHR 1,5 920 /dev/zero t.sh 12779 root 1w FIFO 0,6 32627 pipe t.sh 12779 root 2w REG 8,7 2248 1590776 /home/sgf/new_tree/src/send_sms/2.tmp t.sh 12779 root 9w REG 8,7 43 1121662 /home/sgf/new_tree/src/send_sms/1.tmp # }}} # cat ./1.tmp {{{ f2(): write to stdout f(): write to stdout v: 'f(): write to pipe' # }}}
Комментариев нет:
Отправить комментарий