0

Nesting problem with duck typing:

function f1 { echo $1; } #with argument
function f2 { N=$((0+1)); f$N "fff"; } #with dynamic name

Desired result:

function f2 { { echo $1; } "fff"; } 

NB. sorry I revised the problemtk real codding issues.

How to solve the issue?

4
  • And what should happen for function f3 { f1 arg; } or function f4 { <file f1; } or function f5 { eval f'1 arg'; } or function f6 { var='f1'; "$var"; }? Commented Jun 21, 2023 at 13:51
  • Calling f2 will call f1, running that echo fff. That's what one usually wants. What is the "issue" here?
    – ilkkachu
    Commented Jun 21, 2023 at 17:34
  • maybe because bash is some dynamical interpreter. it evaluate its members at script execution. but it can be resolved by preprocess evaluation and puting the objects in to stack and execute the whole script at once.
    – mr.tee
    Commented Jun 22, 2023 at 12:39
  • 1
    @mr.tee Sorry, no, it can not be resolved by preprocess evaluation, as objects and functions are conditionally declared at runtime and not as in, say, C++ or Rust, at compile time. See my answer! Commented Jun 22, 2023 at 17:39

2 Answers 2

5

That's not something that bash does. It's also not . So, I really wonder what you're trying to do here.

The way Bourne-like shells work is by having a table of callable functions, which actually get expanded only at the point of invocation. So, at the point where f1; gets interpreted, the function f1 gets "called," not earlier, like a macro would be.

Thus, your desired result has little to do with the code you posted above, and is not inherently equivalent. A function has its own context, and its own symbol, which does matter beyond being a {…} scoped collection of statements.

So, what you want is impossible without changing how bash and consorts work - at which point, you're working with a different programming language, more akin to TeX than to shell scripts. (By the way, TeX as a language is … original, and not dominantly so in good ways.)

So, as stated in your question, you actually want to change your program to not work as before, which involved a symbolic lookup in the list of functions when you invoked f1;. This might (and will) break a few more complex script that exploit the ability to re-define functions.

That doesn't sound like a good idea to me, but well, it's your choice. You would need to write a static parser for bash scripts, i.e. one that can understand shell syntax without executing the program. By principle, sadly,

The POSIX shell language defies conventional wisdom of compiler construction on several levels: The shell language was not designed for static parsing, but with an intertwining of syntactic analysis and execution by expansion in mind.

Especially, and emphasis is mine here:

lexical analysis depends on the parsing context and the evaluation context,

(Y. Régis-Gianas, N. Jeannerod, R. Treinen: Morbig: A Static Parser for POSIX Shell)

which means, in general, you cannot statically expand the f1. You need to know what f1 is at the point f2 gets executed!

POSIX shells by design not being static languages (not having anything like a consistent type system) cannot make any guarantees on that. It takes a single conditional containing an alias f1=f3 to change what your program is at runtime! (And there's numerous other mechanism with which you can mess with this.) In other words, the only time you actually know what f1 within f2 really "means" is at each point when f2 gets executed, and you need to know the full state of your shell (and its environment).

Thus, to do what you want, you need to become the shell; you need to modify bash and add functionality that allows you to print (save or log) the expansion of something specific, maybe through a hook.

But maybe what you want is a lot smaller than what you ask for in your question. Maybe you don't want to modify your shell scripts, but just, at any time in your shell script, be able to print the definition of a function? That can be done via declare, i.e., declare -fp f1.

2
  • 2
    Technically, when reading the definition of a function, the shell is able to tell what words are in command position and actually does alias expansion there. So one in theory could define aliases named after the functions and with the body of those functions as their value and re-evaluate the definition of those functions to perform the expansion (which would rarely be a valid thing to do). Commented Jun 21, 2023 at 15:51
  • @StéphaneChazelas I thought in non-interactive shells that was an optional shopt option? But yeah, I always think of alias as defining a macro, whereas function defines something that behaves more like a function in a "proper" programming language. Commented Jun 21, 2023 at 16:48
0

Is this what you are after?

I did make some tweaks to the specs. The function corresponding to the second arg of the command, is loaded into the environment as the function fout. If you run the script with

. duck.sh <string> <function number> 

the function is loaded into the environment of the parent shell. The output shows the function that was selected, and the output, based on the first arg, if there is one.

file duck.sh:

#!/bin/bash

function f1 { echo $1; } #with argument
function f2 { f1; echo "here2 "$1; }
function f3 { f1; f2; echo "The Third "$1; }

function expandInner {
    while [[ $toExpand != "" ]];  do
        while read -r line; do
            getInner $line
        done <<< $toExpand
        toExpand=$(grep -w -o 'f[0-9]' <<< $inner)
    done
}

function getInner {

    tmpString=$(typeset -f $1)
    tmpString=$(echo $tmpString | awk 'BEGIN{RS="}"; FS="{"} {print $2}')
    inner=${inner/${line}/${tmpString}}

}

if [ -z "$2" ] ; then
    N=$((0+1))
else
    N=$2
fi

inner=$(echo $(typeset -f f$N) | awk 'BEGIN{RS="}"; FS="{"} {print $2}')
toExpand=$(grep -w -o 'f[0-9]' <<< $inner)
expandInner
echo "function fout { $inner ; echo \"fff\"; }" | tee ~/.tmp; source ~/.tmp; rm ~/.tmp
fout $1

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .