0

I have to write a bash script that ignores specific strings in variables to prevent file mixups. I am very new to bash and I have no idea how to make a line of code that checks for illegal variable/string combinations.

For example the strings marble and igneous_rocks should never be used together in a for loop. This is the code that needs to be changed:

    #!/bin/bash

IGNEOUS_BLOCK=(aa adakite pahoehoe)
METAMORPHIC_BLOCK=(eclogite marble)
SEDIMENTARY_BLOCK=(argillite chalk jaspillite)
ROCK_TYPE=(igneous_rocks metamorphic_rocks sedimentary_rocks)

#Buttons
  for igneous_block in "${IGNEOUS_BLOCK[@]}" ; do
  for rock_type in "${ROCK_TYPE[@]}" ; do
      printf "{
    \"parent\": \"block/button\",
    \"textures\": {
        \"texture\": \"strata:blocks/"$rock_type"/"$igneous_block"\"
    }
}"> "${igneous_block}_button.json"
  done;
  done;

This is what it should do:

If the varible ROCK_TYPE uses the string igneous_rocks it should only pick strings inside IGNEOUS_BLOCK and not from METAMORPHIC_BLOCK and SEDIMENTARY_BLOCK

This is what I want all the variables work:

ROCK_TYPE cycles through the strings available.

ROCK_TYPE=(igneous_rocks metamorphic_rocks sedimentary_rocks)

IGNEOUS_BLOCK should only be allowed use the string igneous_rocks.

IGNEOUS_BLOCK=(aa adakite pahoehoe)

METAMORPHIC_BLOCK should only be allowed use the string metamorphic_rocks.

METAMORPHIC_BLOCK=(aa adakite pahoehoe)

SEDIMENTARY_BLOCK should only be allowed use the string sedimentary_rocks.

SEDIMENTARY_BLOCK=(aa adakite pahoehoe)

What needs to change in my code to make it work like I want it to?

Quizer9O8
  • 1
  • 1
  • 1
    Two `for` and `do` but only one `done`? Add a shebang and then paste your script there: http://www.shellcheck.net/ – Cyrus Aug 09 '20 at 17:38
  • I changed the code to have two `done` instead of one and added a shebang at the top but I still end up with 'sedimentary_rocks` being used along side 'aa'. How do I fix that? – Quizer9O8 Aug 09 '20 at 18:40
  • Keep going through ShellCheck. The shebang shouldn't be indented, and the double quotes are wrong. But that doesn't seem to be the problem. – wjandrea Aug 09 '20 at 18:47
  • Executing the code in bash seems to be completely fine. ShellCheck only recommends alternatives to write the code. The files do get generated like they should be but the order of strings that's being used inside the code is wrong. Also The double quotes can be used in multiple ways. I also figured out that you can incase most of the code inside either double or single quotes. – Quizer9O8 Aug 09 '20 at 18:55
  • `igneous_rocks it should only pick strings inside IGNEOUS_BLOCK` Why can't you have all of them lowercase or all of them uppercase? What's the point of converting the case? `it should only pick strings` Please post the output you want to have as plain text. `should only be allowed` What does it mean when a variable "is allowed to use"? A variable `SEDIMENTARY_BLOCK` is a variable, not a function, it can't do something. – KamilCuk Aug 09 '20 at 18:58
  • The variable `ROCK_TYPES` contains the strings `igneous_rocks`, `metamorphic_rocks` and `sedimentary_rocks` if for example `igneous_rocks` is picked it i want a way to make sure only the strings `aa`, `adakite` and `pahoehoe` can be picked from the variable `IGNEOUS_BLOCK`. So the output inside the code should be `"texture": "strata:blocks/igneous_rocks/aa"` and not `"texture": "strata:blocks/sedimentary_rocks/aa"` `sedimentary_rocks` is the wrong string and I do not want that to happen. – Quizer9O8 Aug 09 '20 at 19:13
  • 1
    Yes, and I ask, why doesn't _the variables_ aren't named `igneous_rocks=(aa adakite pahoehoe)` but some random `IGNEOUS_BLOCK`? Why would you aim to convert `igneous_rocks` into `IGNEOUS_BLOCK`? Why not name the variable `igneous_rocks` in the first place? – KamilCuk Aug 09 '20 at 19:28
  • @Quizer9O8 It will help if you post expected output. Also, no point in repeating the code (e.g., SEDIMENTARY_BLOCK should only ,,, SEDIMENTARY_BLOCK=(aa adakite pahoehoe)`, it does not help understanding the question – dash-o Aug 09 '20 at 23:56

4 Answers4

1

Rather than generating your client data files with bash/shell, which will bite you back sooner or later, because it is not very portable and hard to maintain, as yet another framework and tools-set to add to your build environment;

You'd better implement data generators with dedicated Gradle tasks as documented by the Minecraft-Forge project here: https://mcforge.readthedocs.io/en/1.14.x/datagen/intro/

If you still really want to do this with bash. Your code can work if you use nameref variables to dynamically reference the corresponding rock types arrays:

#!/usr/bin/env bash

# shellcheck disable=SC2034 # nameref used
igneous_rocks=('aa' 'adakite' 'pahoehoe')
# shellcheck disable=SC2034 # nameref used
metamorphic_rocks=('eclogite' 'marble')
# shellcheck disable=SC2034 # nameref used
sedimentary_rocks=('argillite' 'chalk' 'jaspillite')
rock_types=('igneous_rocks' 'metamorphic_rocks' 'sedimentary_rocks')

#Buttons
declare -n rock_type
for rock_type in "${rock_types[@]}"; do
  for rock_name in "${rock_type[@]}"; do
    cat <<JSON >"${rock_name}_button.json"
{
  "parent": "block/button",
  "textures": {
    "texture": "strata:blocks/${!rock_type}/$rock_name"
  }
}
JSON
  done
done

Example of the generated adakite_button.json:

{
  "parent": "block/button",
  "textures": {
    "texture": "strata:blocks/igneous_rocks/adakite"
  }
}

Or using jq to build the JSON instead of here_document:

#!/usr/bin/env bash

# shellcheck disable=SC2034 # nameref used
igneous_rocks=('aa' 'adakite' 'pahoehoe')
# shellcheck disable=SC2034 # nameref used
metamorphic_rocks=('eclogite' 'marble')
# shellcheck disable=SC2034 # nameref used
sedimentary_rocks=('argillite' 'chalk' 'jaspillite')
rock_types=('igneous_rocks' 'metamorphic_rocks' 'sedimentary_rocks')

#Buttons
declare -n rock_type
for rock_type in "${rock_types[@]}"; do
  for rock_name in "${rock_type[@]}"; do
    jq -n --arg rocktype "${!rock_type}" --arg rockname "$rock_name" \
      '{"parent":"block/button","textures":{"texture":("strata:blocks/"+$rocktype+"/"+$rockname)}}' >"${rock_name}_button.json"
  done
done
Léa Gris
  • 17,497
  • 4
  • 32
  • 41
0

It seems like you want to have nested data, where ROCK_TYPE is an array of types, and each type has an array of subtypes.

In real programming languages, you can have nested data structures which would make this quite easy, but unfortunately Bash only has really ugly solutions for emulating nested data.

Instead, you can use a case statement to define the blocks array to iterate over:

#!/bin/bash

rock_types=(igneous_rocks metamorphic_rocks sedimentary_rocks)

for rock_type in "${rock_types[@]}"; do
    case $rock_type in
    igneous_rocks)
        blocks=(aa adakite pahoehoe)
        ;;
    metamorphic_rocks)
        blocks=(eclogite marble)
        ;;
    sedimentary_rocks)
        blocks=(argillite chalk jaspillite)
        ;;
    esac
    for block in "${blocks[@]}"; do
        echo "$rock_type/$block"
    done
done

Output:

igneous_rocks/aa
igneous_rocks/adakite
igneous_rocks/pahoehoe
metamorphic_rocks/eclogite
metamorphic_rocks/marble
sedimentary_rocks/argillite
sedimentary_rocks/chalk
sedimentary_rocks/jaspillite

BTW, all-caps variable names should be reserved for environment variables like PATH and other special variables like RANDOM.

wjandrea
  • 28,235
  • 9
  • 60
  • 81
0

Assumptions:

  • OP wants to cycle through all rock types listed in the ROCK_TYPE array
  • each rock type and matching *_BLOCK array have matching names with a) the array name being all caps and b) the string rocks replaced with BLOCK

One relatively easy method would be to use a case statement to determine which **_BLOCK array to reference based on the current rock type.

A first pass to verify the idea:

for rtype in "${ROCK_TYPE[@]}"
do
    echo "+++++ rock type = ${rtype}"

    case "${rtype}" in
        igneous_rocks)          printf "%s\n" "${IGNEOUS_BLOCK[@]}"     ;;
        metamorphic_rocks)      printf "%s\n" "${METAMORPHIC_BLOCK[@]}" ;;
        sedimentary_rocks)      printf "%s\n" "${SEDIMENTARY_BLOCK[@]}" ;;
    esac
done

Which generates:

+++++ rock type = igneous_rocks
aa
adakite
pahoehoe
+++++ rock type = metamorphic_rocks
eclogite
marble
+++++ rock type = sedimentary_rocks
argillite
chalk
jaspillite

There are a few ways to modify this to allow for looping through the individual values in an associated *_BLOCK array, eg:

for rtype in "${ROCK_TYPE[@]}"
do
        bname=${rtype//rocks/block}        # OPs example could use this for the 'texture' field and json file name

        for btype in $(case "${rtype}" in
                        igneous_rocks)          echo "${IGNEOUS_BLOCK[@]}"     ;;
                        metamorphic_rocks)      echo "${METAMORPHIC_BLOCK[@]}" ;;
                        sedimentary_rocks)      echo "${SEDIMENTARY_BLOCK[@]}" ;;
                       esac)
        do
                echo ".${rtype}.${bname}.${btype}."
        done
done

Which generates:

.igneous_rocks.igneous_block.aa.
.igneous_rocks.igneous_block.adakite.
.igneous_rocks.igneous_block.pahoehoe.
.metamorphic_rocks.metamorphic_block.eclogite.
.metamorphic_rocks.metamorphic_block.marble.
.sedimentary_rocks.sedimentary_block.argillite.
.sedimentary_rocks.sedimentary_block.chalk.
.sedimentary_rocks.sedimentary_block.jaspillite.

NOTE: periods added as visual delimiters


Another idea would be to use a nameref (ie, the value of one variable is used as the name of another variable).

NOTE: I'm including this option since the example shows similarly named rock types and block names, which in turn makes a nameref solution relatively easy to implement.

Consider the following (simple) example:

$ unset x h
$ x=5 h=x
$ echo $x
5
$ echo $h
x
$ typeset -n h       # define nameref
$ echo $h
5
$ x=10
$ echo $h
10

Alternatively:

$ unset x h
$ x=5
$ typeset -n h=x     # define nameref
$ echo $x
5
$ echo $h
5
$ x=10
$ echo $h
10

One idea on using namerefs for the rock/block problem:

for rtype in "${ROCK_TYPE[@]}"
do
        bname=${rtype//rocks/block}                     # again, use this for the 'texture' field and json file name

        typeset -u -n currblock=${rtype//rocks/BLOCK}   # define nameref, convert to all uppercase and replace 'rocks' with 'BLOCK'

        for btype in "${currblock[@]}"                  # loop through nameref'd values
        do
                echo ".${rtype}.${bname}.${btype}."
        done
done

Which generates the same set of output as the case solution:

.igneous_rocks.igneous_block.aa.
.igneous_rocks.igneous_block.adakite.
.igneous_rocks.igneous_block.pahoehoe.
.metamorphic_rocks.metamorphic_block.eclogite.
.metamorphic_rocks.metamorphic_block.marble.
.sedimentary_rocks.sedimentary_block.argillite.
.sedimentary_rocks.sedimentary_block.chalk.
.sedimentary_rocks.sedimentary_block.jaspillite.

NOTE: again, periods added as visual delimiters

markp-fuso
  • 28,790
  • 4
  • 16
  • 36
0

The following code:

IGNEOUS_BLOCK=(aa adakite pahoehoe)
METAMORPHIC_BLOCK=(eclogite marble)
SEDIMENTARY_BLOCK=(argillite chalk jaspillite)
ROCK_TYPE=(igneous_rocks metamorphic_rocks sedimentary_rocks)
for i in "${ROCK_TYPE[@]}"; do
    # convert to upercase, replace rocks
    n="${i//_rocks/_BLOCK}"
    n="${n^^}[@]"
    # Using indirect expansion
    for j in "${!n}"; do
       echo "$i/$j"
    done
done

would output:

igneous_rocks/aa
igneous_rocks/adakite
igneous_rocks/pahoehoe
metamorphic_rocks/eclogite
metamorphic_rocks/marble
sedimentary_rocks/argillite
sedimentary_rocks/chalk
sedimentary_rocks/jaspillite

Notes: upper case variables are by convention used for exported variables like PWD LINES COLUMNS UID EID TERM LC_ALL COUNTRY HISTSIZE etc. Prefer using lower case variables in your scripts.

You could name variables same way as array elements, saving you from some code:

igneous_rocks=(aa adakite pahoehoe)
metamorphic_rocks=(eclogite marble)
sedimentary_rocks=(argillite chalk jaspillite)
rock_types=(igneous_rocks metamorphic_rocks sedimentary_rocks)
for i in "${rock_types[@]}"; do
    n="${i}[@]"
    for j in "${!n}"; do
       echo "$i/$j"
    done
done

As an alternative to indirect expansion there is also a namereference.

igneous_rocks=(aa adakite pahoehoe)
metamorphic_rocks=(eclogite marble)
sedimentary_rocks=(argillite chalk jaspillite)
rock_types=(igneous_rocks metamorphic_rocks sedimentary_rocks)
for i in "${rock_types[@]}"; do
    declare -n n="$i"
    for j in "${n[@]}"; do
       echo "$i/$j"
    done
done
KamilCuk
  • 120,984
  • 8
  • 59
  • 111
  • I took a look at the code solution you posted and I applied this to create `.json` files quite effectively. I think this is the method I will use from now on. – Quizer9O8 Aug 09 '20 at 19:34
  • I also suggest you use `jq` to compose your JSON rather than concatenate strings. – Léa Gris Aug 09 '20 at 20:25
  • The First code works perfectly fine but I have one tiny problem. I changed all my variables from lowercase to uppercase letters and now nothing happens. I found out this code `n="${r//_rocks/_block}"` seems to be the problem. Only if I keep in the lower case letters `n="${r//_rocks/_BLOCK}"` including variable lowercase letters it seems to work. How can I use just uppercase letters without the script doing nothing? – Quizer9O8 Aug 13 '20 at 14:29
  • Sorry, I do not think anything can save you from understanding what the code actually does. [shell paramater expansion](https://www.gnu.org/software/bash/manual/bash.html#Shell-Parameter-Expansion). If you changed it upper case, you have to change the values of `rock_types` to upper case to. Or you could upper-case-it with `${parameter^^pattern}` expansion. Other then that, please show the code. – KamilCuk Aug 13 '20 at 14:33