WelcomeWelcome | FAQFAQ | DownloadsDownloads | WikiWiki

Author Topic: in a script, could I have the main body folowed by its functions at the botom?  (Read 2449 times)

Offline nick65go

  • Hero Member
  • *****
  • Posts: 832
Hi, sorry for maybe a novice question.
I would like to typically define the main logic prior to defining its inner sub-functions, so someone reading the code will focus on the main logic first. Often this takes the form of creating a "main" function at the top of the file, and invoking it at the bottom. I mean just write code from top-to-bottom, sprinkling in functions as needed at the bottom, after main code/function exit?

Is there any lost speed in sh-type scripts like these? Or any memory issues not-initialized variables?
« Last Edit: February 25, 2023, 05:27:29 AM by nick65go »

Offline curaga

  • Administrator
  • Hero Member
  • *****
  • Posts: 11041
Should be fine to write like that.
The only barriers that can stop you are the ones you create yourself.

Offline Rich

  • Administrator
  • Hero Member
  • *****
  • Posts: 11576
Hi nick65go
I think I tried something similar but received errors because I was
trying to call functions before they were defined. You can get
around this by sourcing a file containing your functions at the
top of your script.

Offline GNUser

  • Wiki Author
  • Hero Member
  • *****
  • Posts: 1497
Hi, nick65go and Rich. Sure it's possible to define a main function at the top of a shell script followed by ancillary functions!

The shell reads shell scripts from top to bottom. If you call the main function at the bottom of the script, at that point the shell will have seen all the function definitions. Try this trivial example--call it HardcodedGreetings.sh:

Code: [Select]
#!/bin/sh

main()
{
say_hi
say_bye
}

say_hi()
{
echo "hi"
}

say_bye()
{
echo "bye"
}

main

The script works exactly as expected:
Code: [Select]
$ ./HardcodedGreetings.sh
hi
bye

If the script has arguments, you need to remember to pass the script's arguments to the main function. Here is a trivial example to illustrate this--let's call this one CustomGreeting.sh:

Code: [Select]
#!/bin/sh

main()
{
say_arguments "$@"
}

say_arguments()
{
echo "$@"
}

main "$@"

Again, it works exactly as expected:
Code: [Select]
$ ./CustomGreeting.sh testing 1 2 3
testing 1 2 3
« Last Edit: February 25, 2023, 04:21:25 PM by GNUser »

Offline nick65go

  • Hero Member
  • *****
  • Posts: 832
@GNUser: Thanks, very good that you gave the examples. Also good that there is no penalty in execution speed.

Offline Rich

  • Administrator
  • Hero Member
  • *****
  • Posts: 11576
Hi GNUser
Well done, thank you.  :)

Offline GNUser

  • Wiki Author
  • Hero Member
  • *****
  • Posts: 1497
Hi Rich. You're welcome. It's rare that the help flows in that direction :)

Hi nick65go. You're welcome. Glad I could help. Actually, here's a better example of CustomGreeting.sh that shows what's going on a bit more clearly:

Code: [Select]
#!/bin/sh

main()
{
say_hi "$1"
say_bye "$2"
}

say_hi()
{
echo "hi $1"
}

say_bye()
{
echo "bye $1"
}

main "$@"

Code: [Select]
$ ./CustomGreeting.sh "John" "Mr. John Doe"
hi John
bye Mr. John Doe
Happy Hacking!




« Last Edit: February 25, 2023, 08:00:51 PM by GNUser »

Offline mocore

  • Hero Member
  • *****
  • Posts: 633
  • ~.~
 
Code: [Select]
#!/bin/sh

main()
{
say_hi "$1"
say_bye "$2"
}

say_hi()
{
echo "hi $1"
}

say_bye()
{
echo "bye $1"
}

 for argx in "${@}" ; do
  case  $argx in
   m|main) shift ; main "${@}" ; exit 0 ;;
   # other args
   *) return 0 ;;
  esac ; done
 # no args
 [ $# -eq 0 ] && return 0 ;

with the above addition ( worked out in this (messy) topic http://forum.tinycorelinux.net/index.php/topic,26133.msg167895.html )
its possible to source the script to access the functions within shell

which imho can be useful for testing / composing new  functions / cases .

Offline Rich

  • Administrator
  • Hero Member
  • *****
  • Posts: 11576
Hi mocore
... its possible to source the script to access the functions within shell ...
Do you mean the way you can call functions from  /etc/init.d/tc-functions  from the command line:
Code: [Select]
tc@E310:~$ echo $MIRROR

tc@E310:~$ getMirror
tc@E310:~$ echo $MIRROR
http://repo.tinycorelinux.net/10.x/x86/tcz
tc@E310:~$

Because it's sourced in  ~/.ashrc:
Code: [Select]
tc@E310:~$ head -n 4 .ashrc
# ~/.ashrc: Executed by SHells.
#
. /etc/init.d/tc-functions
if [ -n "$DISPLAY" ]
tc@E310:~$

Offline mocore

  • Hero Member
  • *****
  • Posts: 633
  • ~.~

 @rich
>Do you mean the way you can call functions from  /etc/init.d/tc-functions  from the command line:

yes like that callable in shell via sourcing a script

but  while also  avoiding calling the "main" function when sourced 

id thaught i had not added an example after posting so...

Code: [Select]
# source & use functions in shell
. ./CustomGreeting.sh
say_hi foobar

# or
./CustomGreeting.sh main ; # call script and run the man function


an example
replacing
> main "$@"
in https://git.savannah.gnu.org/cgit/guix.git/plain/etc/guix-install.sh
with 
Code: [Select]
for argx in "${@}" ; do
  case  $argx in
   m|main) shift ; main "${@}" ; exit 0 ;;
   # other args
   *) return 0 ;;
  esac ; done
 # no args
 [ $# -eq 0 ] && return 0 ;

allows ". source" of the script without ruining main

then functions or function inputs or environment can be tested / altered  / ect

Offline Rich

  • Administrator
  • Hero Member
  • *****
  • Posts: 11576
Hi mocore
You can source right from the command line.
A file containing some functions:
Code: [Select]
tc@E310:~$ ls -l Sourcing
-rw-rw-r-- 1 tc staff 20 Mar  6 09:31 Sourcing
tc@E310:~$ cat Sourcing
xyzzy()
{
cal $1
}

Mem()
{
free "-mo"
}

Path()
{
echo $PATH
}
tc@E310:~$
No shebang, not executable, no main, just 3 functions.

The commands work after we source the file:
Code: [Select]
tc@E310:~$ xyzzy
sh: xyzzy: not found
tc@E310:~$ xyzzy apr
sh: xyzzy: not found
tc@E310:~$ Mem
sh: Mem: not found
tc@E310:~$ Path
sh: Path: not found
tc@E310:~$ . ./Sourcing
tc@E310:~$ xyzzy
     March 2023     
Su Mo Tu We Th Fr Sa
          1  2  3  4
 5  6  7  8  9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31   
                   
tc@E310:~$ xyzzy apr
     April 2023     
Su Mo Tu We Th Fr Sa
                   1
 2  3  4  5  6  7  8
 9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30                 
tc@E310:~$ Mem
             total       used       free     shared    buffers     cached
Mem:          3028       1477       1551          0         82        574
Swap:          999          0        999
tc@E310:~$ Path
/home/tc/.local/bin:/usr/local/sbin:/usr/local/bin:/apps/bin:/usr/sbin:/usr/bin:/sbin:/bin:/etc/sysconfig/tcedir/ondemand
tc@E310:~$

Offline mocore

  • Hero Member
  • *****
  • Posts: 633
  • ~.~
hi @rich
wrt title : in a script, could I have the main body folowed by its functions at the botom?

you can order functions in the script however you like

the  order the of the named  blocks in the script should not affect the script execution
unless a function is called before its defined
which will result in error
Code: [Select]
$script: line $X: $funcname: not found

as mentioned : https://forum.tinycorelinux.net/index.php/topic,26125.msg167737.html#msg167737

the example
adding an optional explicit case to call the main function then exit
and two return cases , one for no arguments and , one for all other args

allows changing script execution path making calling the main function optional
so other functions can be sourced into shell or other scripts

perhaps its considered better to have ?? (  that seams to be  the gist of your reply   )
lib-foo.sh
and
foo.sh

where foo.sh effectively encapsulates  main block
and lib-foo.sh holds the functions to import