0

Say I've got the following javascript:

    const something = doSomething();
    
    function doSomething() {
        console.log("About to do something");
        
        const a = doSomethingElse();
        if (a == "dog") {
            console.log("Worked");
            console.log("Now let's do xyz");
            return "cat";
        } else {
            console.log("failed for some reason");
            return "horse";
        }
    }
    
    function doSomethingElse() {
        console.log("About to do more");
        console.log("Done doing more stuff");
        
        return "dog";
    }

How can we achieve this in bash ? And I want to view the logs in real time, not store them in a file for later ? I'm thinking about something like the following, but then all gets mixed together, plus the return keyword in bash serves other purposes and can't accept strings.

    something=$(doSomething);
    
    function doSomething() {
        console_log "About to do something";
        
        a=$(doSomethingElse);
        if [[$a = "dog"]]; then
            console_log "Worked";
            console_log "Now let's do xyz";
            echo "cat"; # Wish I could do return "cat" ...
        else
            console_log "failed for some reason";
            echo "horse"; # Wish I could do return "horse" ...
        fi;
    }
    
    function doSomethingElse() {
        console_log "About to do more";
        console_log "Done doing more stuff";
        
        echo 'dog'; #Wish I could do return "dog" ...
    }
    
    function console_log() {
        #Is echo the right thing, or tee, or something else ?
        echo $1;
    }

Isn't there multiple output streams to achieve this ? Or, is there an alternative to $() to assign variables ? I have tried to play with tee, &*, &? and 3>1 stuff and I can't make sense of all this... :(

I have bash 4.2.46 under CentOS 7.9

thanks for your help :)

1
  • @fra-san sorry, it's not to port JS code, I was trying to find a way to explain what I'm trying to do using non-shell languages. ibuprofen pointed out to printf with >&2, which is exactly what I needed. thanks for your time Jun 23, 2021 at 1:18

1 Answer 1

0

This should be OK:

#! /bin/bash -

doSomething() {
    print_err "About to do something"

    a="$(doSomethingElse)"
    if [[ "$a" = "dog" ]]; then
        print_err "Worked"
        print_err "Now let's do xyz"
        printf '%s' "cat"
    else
        print_err "failed for some reason"
        printf '%s' "horse"
    fi
}

doSomethingElse() {
    print_err "About to do more";
    print_err "Done doing more stuff";
    printf '%s' 'dog'
}

print_err() {
    # Print to stderr as we do this in functions
    # that print result to stdout
    printf '%s\n' "$1" >&2
}

something="$(doSomething)"
# Making it readonly would be somewhat similar to const
readonly something

printf '%s\n' "$something"

Needed:

In general:

Beyond that, replacing [[ with [ would make it sh compliant. What does it mean to be "sh compatible"?

Also look into scopes of variables. Is there a way to make the local command work portably to dash ksh bash and zsh?

As in, you could have, done:

set_something() {
    something='cat'
}

set_something
printf 'something="%s"\n' "$something"

Streams

JavaScript does not work with streams, shell does. Simplified it:

  • read from stdin, file-descriptor 0 (standard in)
  • write to stdout, file-descriptor 1 (standard out)
  • write to stderr, file-descriptor 2 (standard error)

Beyond that one can open new, read / write files etc.

Might of interest How portable are /dev/stdin, /dev/stdout and /dev/stderr?


Return values.

Bash functions does not "return" strings. When one do:

foo="$(some command)"

one assign the standard output of some command to foo. If one do not change the redirect of non-data prints in your code, you can see this by:

# In doSomething():
    printf 'a="%s"\n' "$a" >&2

# At end:
printf '"%s"\n' "$something"

You would get:

a="About to do more
Done doing more stuff
dog"
something="About to do something
failed for some reason
horse"

As you see, the entire output is assigned.

After "fix" one could capture both stdout and stderr by redirecting stderr to stdout:

foo="$(some command 2>&1)"

Exit code

Shell functions do, however, have exit codes, just like any program. If not explicitly returning a value, it's the exit status of the last command executed in that function. 0 is OK, anything else is considered an error in tests.

Simple example:

#! /bin/bash -

foo() {
    printf 'I am foo\n'
    return 1
}
greetings() {
    if [ "$1" = "hi" ]; then
        printf 'Hello\n'
    else
        printf 'I do not know what to do with %s\n' "$1"
        return 12
    fi
}
if foo; then
    printf 'OK\n'
else
    printf 'FAIL\n'
fi

printf '\n## Try greetings "yo":\n'
greetings yo
ecode=$?
printf 'greetings() returned %d\n' "$ecode"

printf '\n## Try greetings "hi":\n'
greetings hi
ecode=$?
printf 'greetings() returned %d\n' "$ecode"

Result:

I am foo
FAIL

## Try greetings "yo":
I do not know what to do with yo
greetings() returned 12

## Try greetings "hi":
Hello
greetings() returned 0

Some help by shellcheck

Not fool-proof but can be great help with linting scripts.

You can use it as a command line tool, paste code online, or incorporate it into your editor.

For example if you use Vim with ALE, it picks up shellcheck if installed. Be aware though that packages bundled with distributions can be somewhat outdated. Ubuntu 18.04, for example, uses v0.4.6

https://github.com/koalaman/shellcheck

Shell scripts in general

Playing around with bash et al. can be useful for learning. Always consider what you want to do. text-processing for example is almost always better done with other tools. Then one can use shell-scripts to glue it all together. Use pipes etc. - part of the power of the unices. Here under for example sort, join, cut, tr, paste, uniq, find, grep, cat, date, tail, head, xargs, wc, fold, column, paste, and so on.

Interactive shell scripts has it use as well as expanding your shell with various functionality.

With more advanced data-types and complexity use things like perl, python, and a wide range of others or even c. c is in general a very nice way to learn about the system at a lower level.

awk, sed etc. are also very powerful and useful for processing text where awk is the easier to learn.

6
  • Wow that's an amazing explanation :O I've tried and it worked ! many thanks. Now, I'm trying to have my whole script die when any nested sub shell "throws an error", but part of the higher shells keep on running. I've seen something like: PROCESS="$$"; set -E; trap '[ "$?" -ne 77 ] || kill -10 $PROCESS'; And do exit 77; in sub shells, but it doesn't work. For now I'm doing kill -9 0; ... Jun 23, 2021 at 1:19
  • @mboisvert-bravad heh, I do not quite like it myself and was about to delete it, but hope you get some out of it. A bit too much all over the place perhaps.
    – ibuprofen
    Jun 23, 2021 at 1:21
  • sorry my reply got cut, I've made edits to it :P Your time is really appreciated Jun 23, 2021 at 1:24
  • @mboisvert-bravad Depends on what you are doing and how you exit / abort. At top level you simply do for example exit 1 to abort script with error code 1. You might want set -e, or set -E; trap 'exit 1' ERR gnu.org/software/bash/manual/html_node/The-Set-Builtin.html - in general, if you do not figure it out write a sample script and post a question. There is a lot of Q/A on the topic already as well.
    – ibuprofen
    Jun 23, 2021 at 1:42
  • "Shell functions do, however, have exit codes, just like any program. If not stated it is zero." If not explicitly returning a value, it's the exit status of the last command executed in that function, not 0.
    – muru
    Jun 23, 2021 at 3:11

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.