Replace ash with bash

Hello Everyone,

when building from source and selecting "bash" in Utilites --> Shells (or something like that), will it totally replace ash without any problems? I mean do I need to edit/change something in order to have a working bash in my future build?

Cheers

bash is an optional package, it won't replace anything by itself (and you wouldn't want it to on slow embedded devices either).

1 Like

I believe I've read somewhere that replacing default shell is not recommended, but I've been doing this: sed -i 's|/bin/ash|/bin/bash|g' /etc/passwd even since 17.01 and it's been working fine (assuming bash is installed).

1 Like

and doing that will brick the device on the next sysupgrade (which won't ship with bash preinstalled, leaving you without a valid login shell); while asu/ chef might help with that, it's against good practices and risky.

4 Likes

This sounds bad.
My use case would have been using arrays in ash but I only found this: Arrays in ash shell?
Since the post 2 years has passed, does ash still unable to handle a normal array? I mean not with workarounds found in the mentioned post.

BTW, if I install bash and use #/bin/env bash as a shebang in my scripts, then my router won't be bricked and my scripts' functionality will be expanded, right (I'll be able to use everything bash is able to)?

should be able to use the online/offline imagebuilder to add the bash package to your
sysupgrade image, perhaps that's not good enough ... ?

1 Like

Can you share? Because with a bit of fiddling you can have something that comes close to arrays. Populate a single variable with space separated values, then read out that value. E.g.:

_VARIABLE="a b c d e f"

# Note the lack of quoting here!
for i in $_VARIABLE
do
   echo "$i is part of this poor man's array."
done

Might not be clean, but it gets the job done for me.

your example is a list not an array: how would you access _VARIABLE[ 2 ]?

busybox ash may stil missing array support so need some kind of workaround.

1 Like

Arrays are not part of the requirements for a POSIX/ SUSv4 compliant shell, accordingly ash isn't likely to gain those non-standard features.

True. Nevermind me.

eval lets you do plenty of things using auxiliary functions.
Hope this helps:

OPTS="a b c d"
DAYS="Monday Tuesday Wednesday Thursday Friday Saturday Sunday"
SIZES="5mm 10mm 15mm 20mm 15mm 30mm"

function init_array {
        SZ=0
        eval ARR=\$${VAR}S
        for i in $ARR; do
                eval $VAR$SZ=$i
                eval echo "New element: $VAR$SZ = \$$VAR$SZ"
                SZ=$(($SZ + 1))
        done
        eval ${VAR}_SZ=$SZ
        eval echo "Total elements in ${VAR}S: \$${VAR}_SZ"
}


echo "Testing variable OPTS"
VAR="OPT"; init_array
echo "First element: $OPT0"
eval echo "Last element: \$OPT$(($OPT_SZ - 1))"

echo
echo "Testing variable DAYS"
VAR="DAY"; init_array
echo "First element: $DAY0"
eval echo "Last element: \$DAY$(($DAY_SZ - 1))"

echo
echo "Testing variable SIZES"
VAR="SIZE"; init_array
echo "First element: $SIZE0"
eval echo "Last element: \$SIZE$(($SIZE_SZ - 1))"

This are the contents that will get converted into a pseudo-array, used only for initialization:

SIZES="5mm 10mm 15mm 20mm 15mm 30mm"

"init_array" creates a list numbered variables following "VAR" name.
"VAR" content must be the array name but without the last 'S':

VAR="SIZE"
init_array

This will copy all elements of SIZES into SIZE0, SIZE1, SIZE2...
And you'll have the number of array elements at SIZE_SZ.

Then you can access the pseudo array by directly referencing SIZEn (ex. $SIZE0) or using more eval expressions when using a count variable in a loop, like:

i=0; while [ $i -lt $OPT_SZ ]; do
        eval echo "OPT$i is \$OPT$i"
        i=$(( $i + 1 ))
done
OPT0 is a
OPT1 is b
OPT2 is c
OPT3 is d

Output of this script:

# ./test2
Testing variable OPTS
New element: OPT0 = a
New element: OPT1 = b
New element: OPT2 = c
New element: OPT3 = d
Total elements in OPTS: 4
First element: a
Last element: d

Testing variable DAYS
New element: DAY0 = Monday
New element: DAY1 = Tuesday
New element: DAY2 = Wednesday
New element: DAY3 = Thursday
New element: DAY4 = Friday
New element: DAY5 = Saturday
New element: DAY6 = Sunday
Total elements in DAYS: 7
First element: Monday
Last element: Sunday

Testing variable SIZES
New element: SIZE0 = 5mm
New element: SIZE1 = 10mm
New element: SIZE2 = 15mm
New element: SIZE3 = 20mm
New element: SIZE4 = 15mm
New element: SIZE5 = 30mm
Total elements in SIZES: 6
First element: 5mm
Last element: 30mm
6 Likes

Impressive :smile:

1 Like

@zsolti Apart from the proposals given above, stackoverflow et al. should have some inspiration for you to solve your problem.

2 Likes

Yes, precisely, thank you for reminding why it's dangerous!

I use my own images with all packages baked in so I never sysupgrade to a vanilla image.

For what I an array wanted to use is to check whether it contains a value or not.
My array should contain values like VAR='"1 pcs banana" "3 pcs apple" "a bit of ananas"' (I don't know whether my syntax is ok or not).

Wow, this looks a bit complex at first glance, at least for me. Does this all need to check, whether a value is present in my "array" or not?

First of all, I'm just an amateur at linux stuff..
I touch a bit of everything, I'm usually lucky enough to find what I'm searching for, but definitely not an advanced user or scripting master :slight_smile:

The problem with that pseudo-array is you can't have spaces or it will treat as another variable.
But there's a workaround by specifying the Internal Field Separastor (IFS) the shell uses, which is a space by default.
For example, you could use a semicolon(; ), a colon(: ) or even a newline char, then the spaces would be no longer used as separators:

# Setting colon as separator
IFS=$':'

VAR="Hello there:I like fried chicken:With french fries"

# You also can put a \ to concatenate the next line (Avoiding inserting a new line),
# making easier to handle, insert and remove variables

VAR="\
Hello there:\
I like fried chicken:\
With french fries\
"

for i in $VAR; do
        echo $i
done
# ./test
Hello there
I like fried chicken
With french fries

Of course you can't insert a variable that includes the same char you're using as separator.
But you can set the separator to basically anything.
For example, new line itself, making it even easier!

IFS=$'\n'
VAR="
Hello there
I like fried chicken
With french fries
"
# ./test
Hello there
I like fried chicken
With french fries

Following the first example, the added spaces caused some issues, requiring a bit of tweaking (Note the added apostrophe characters around ' $i ' ).

IFS=$'\n'
OPTS="
Option A
Option B
Option C
Option D
"

function init_array {
        SZ=0
        eval ARR=\$${VAR}S
        for i in $ARR; do
                eval "$VAR$SZ"='$i'
                eval echo "New element: $VAR$SZ = \$$VAR$SZ"
                SZ=$(($SZ + 1))
        done
        eval ${VAR}_SZ=$SZ
        eval echo "Total elements in ${VAR}S: \$${VAR}_SZ"
}

echo "Testing variable OPTS"
VAR="OPT"; init_array
echo "First element: $OPT0"
eval echo "Last element: \$OPT$(($OPT_SZ - 1))"
# ./test2
Testing variable OPTS
New element: OPT0 = Option A
New element: OPT1 = Option B
New element: OPT2 = Option C
New element: OPT3 = Option D
Total elements in OPTS: 4
First element: Option A
Last element: Option D

Now all it takes is testing! That's what all this is about, experimenting, trial and error.
Eventually you'll get your script done.
The basics are dead easy:

  • Create the variable with the initialization data called NAMES
  • Create the pseudo-array by setting VAR='NAME', then calling init_array
  • Directly accessing any element by NAMEn:
    myvar="$NAME0"
    echo "$NAME0"
  • Indexed access using indexing number i (Requiring use of eval):
    eval myvar="\$$NAME$i"
    eval echo "\$$NAME$i"
  • Finding a variable index, i.e. "Option C" on OPTS
i=0; while [ $i -lt $OPT_SZ  ]; do
        if [ "$(eval echo \$OPT$i)" == "Option C" ]; then
                echo found \"Option C\" at index $i
        fi
        i=$(($i + 1))
done
found "Option C" at index 2
  • Remember your variables are strings, so you can check whether any exists or not by checking if it's empty:

    If exists:
    if [ "$NAME0" ]; then
    If it doesn't exist:
    if [ -z "$NAME0" ]; then

    Now the same with indexed method:
    If exists:
    if [ "$(eval echo \$NAME$i)" ]; then
    If it doesn't exit:
    if [ -z "$(eval echo \$NAME$i)" ]; then




The \ before $ basically disables the referencing.
So:

  • $NAME tells the shell "Give the contents of the variable NAME"
  • \$NAME writes literally "$NAME".

So, for NAME0="Thomas", i=0;

echo $NAME0 -> Show variable NAME0
>Thomas

echo $NAME$i -> Show variable NAME (Doesn't exist) and variable i(0)
>0

echo \$NAME$i -> Show text $NAME and variable i(0)
>$NAME0

eval echo \$NAME$i -> Evaluate "echo $NAME0", now it will show variable NAME0
>Thomas
5 Likes

:hushed:

This is what I call an explanation!!! Many thanks, I will take a look on it once I have time!
It is extremely helpful!

One thing came across my mind: what if I would like to populate a blank array with values of which there could be more of the same value? How could it be possible?

So let's say ARR="" and I want to populate it with apple, banana, cherry, melon, melon, apple, apple and list its content in a fashion uniq -c does?

Sorry, I have no idea, can't help with that!