4

I have two mappings file like this as shown below:

primary_mapping.txt

{1=[343, 0, 686, 1372, 882, 196], 2=[687, 1, 1373, 883, 197, 736, 1030, 1569], 3=[1374, 2, 884, 737, 198, 1570], 4=[1375, 1032, 1424, 3, 885, 1228], 5=[1033, 1425, 4, 200, 886]}

secondary_mapping.txt

{1=[1152, 816, 1488, 336, 1008], 2=[1153, 0, 817, 337, 1489, 1009, 1297], 3=[1, 1154, 1490, 338], 4=[1155, 2, 339, 1491, 819, 1299, 1635], 5=[820, 1492, 340, 3, 1156]}

In the above mapping files, each clientId has primary and secondary mapping. For example: clientId 1 has 343, 0, 686, 1372, 882, 196 primary mapping and 1152, 816, 1488, 336, 1008 secondary mapping. Similarly for other clientIds as well.

Below is my shell script in which it prints primary and secondary mapping for a particular clientid:

#!/bin/bash
mapfiles=(primary-mappings.txt secondary-mappings.txt)

declare -a arr

mappingsByClientID () {
  id=$1 # 1 to 5 
  file=${mapfiles[$2]} # 0 to 1
  arr=($(sed -r "s/.*\b${id}=\[([^]\]+).*/\1/; s/,/ /g" $file))
  echo "${arr[@]}"
}

# assign output of function to an array
# this prints mappings for clientid 3. In general I will take this parameter from command line.
pri=($(mappingsByClientID 3 0))
snd=($(mappingsByClientID 3 1))

Now let's say if we can't find primary or secondary mapping for a particular clientid then I want to exit from the shell script with nonzero status code by logging message. I tried exiting from subshell and it didn't worked for me. Is this possible to do?

3
  • It is, research how to get exit code from a subshell. Commented Apr 21, 2018 at 3:40
  • 3
    The ideal practice here is indirect assignment. That is, make your function's usage mappingsByClientID pri 3 0 or mappingsByClientID snd 3 1; then it doesn't need to run in a subshell, so its exit exits the real interpreter. Commented Apr 21, 2018 at 4:15
  • 2
    declare -n arr="$1" will make the variable arr an alias for whatever variable was named in your first parameter, making the above easy. Have fun. Commented Apr 21, 2018 at 4:16

1 Answer 1

2

You can do this (incorporated all the great suggestions of our guru, Charles Duffy):

mappingsByClientID () {
  (($# != 3)) && { echo "Insufficient arguments" >&2; exit 1; }
  declare -n arr=$1    # for indirect assignment (need **Bash 4.3 or above**)
  id=$2                # 1 to 5 
  file=${mapfiles[$3]} # 0 to 1
  [[ $file ]]    || { echo "No mapping file found for id '$id', type '$2'" >&2; exit 1; }
  [[ -f $file ]] || { echo "File '$file' does not exist" >&2; exit 1; }
  # Note: the word boundary `\b` is not supported in ERE
  # See post: https://stackoverflow.com/q/27476347/6862601
  if ! grep -q "[{ ]$id=" "$file"; then
      echo "Couldn't find mapping for id '$id' in file '$file'" >&2
      exit 1
  fi
  mapfile -t arr < <(sed -r "s/.*[{ ]$id=\[([^]\]+).*/\1/" "$file" | tr -s '[ ,]' '\n')
  if ((${#arr[@]} == 0)); then
      echo "Couldn't find mapping for id '$id' in file '$file'" >&2
      exit 1
  fi
  echo "${arr[@]}"
}

Now call the function without the subshell $() so that the exit inside the function will actually exit the script:

mappingsByClientID pri 3 0
mappingsByClientID sec 3 1

It is a better practice to do the error checks in the function.

If you don't want the function to exit, you can check the array size in the caller code, after calling the function.


If you are on a version of Bash that doesn't support namerefs, you can use global variables for arrays, let's says arr is the global, then:

arr=()                   # initialize the global
mappingsByClientID 3 0
pri=("${arr[@]}")        # make a copy of the global array into pri
mappingsByClientID 3 1
sec=("${arr[@]}")        # make a copy of the global array into sec

Modify the mappingsByClientID accordingly to use the global variable instead of the nameref.


Related:

Sign up to request clarification or add additional context in comments.

14 Comments

grumble re: copying several of OP's bad practices (unquoted expansion for string-splitting rather than read -a, mising quotes, etc) rather than demonstrating how to repair them.
@codeforester, ...you're only assigning to arr, not assigning to an array of the OP's choice. If you want your output to be assigned elsewhere, you need to either (1) use an indirect assignment, or (2) rely on the OP to call this in a subshell and read its output (in which case the exit doesn't without the parent process's cooperation... of course, such cooperation is a feasible option). And btw, the error message should go to stderr to prevent its capture.
@codeforester sorry I was away for a while. I tried with your suggestion and looks like it prints out everything if we don't have any mapping for a particular clientid but instead it should print this line Couldn't find mapping for id '$id' in file '$file'. Any thoughts what could be wrong?
That was an issue in your original code. We needed an explicit check to make sure the desired mapping is indeed present in the file before running sed to extract the matched portion. I added the necessary code. We should be good now.
@codeforester I have one last question. I found something very strange today. If I try to iterate this pri array then it doesn't work at all: for item in "${pri[@]}"; do echo "$item" done. I mean it prints all the elements of that array instead of iterating them?
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.