{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/misc/bash-namerefs/",
  "description": "Master Bash namerefs with declare -n to create dynamic variable references. Build generic functions for arrays and associative arrays without eval.",
  "path": "/misc/bash-namerefs/",
  "publishedAt": "2024-09-20T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Shell",
    "Unix",
    "TIL",
    "CLI"
  ],
  "textContent": "While going through a script at work today, I came across Bash's nameref feature. It uses\ndeclare -n ref=\"$1\" to set up a variable that allows you to reference another variable by\nname - kind of like pass-by-reference in C. I'm pretty sure I've seen it before, but I\nprobably just skimmed over it.\n\nAs I dug into the [man pages], I realized there's a gap in my understanding of how variable\nreferences actually work in Bash - probably because I never gave it proper attention and\njust got by cobbling together scripts.\n\nNamerefs\n\nBy default, Bash variables are global unless declared as local within a function. However,\nwhen you pass variables as arguments to a function, they are accessed via positional\nparameters like $1, $2, etc., and any changes to these parameters inside the function do\nnot affect the original variables outside the function.\n\nNamerefs allow you to essentially define a pointer to another variable. By creating a\nnameref, you can indirectly reference and manipulate the target variable without knowing its\nname beforehand. This is incredibly useful for writing generic functions that can operate on\ndifferent variables based on input parameters.\n\nBasic usage\n\nHere's an example:\n\nRunning this will print:\n\nBy running the create_ref function, we can dynamically update the value of\n$original_var, which exists outside of it. Notice that the function doesn't even need to\nknow about $original_var; it works on any variable name provided, making it generic.\n\nIn this script:\n\n- We declare a variable original_var with the value \"Hello, World!\".\n- The create_ref function takes the name of a variable as an argument.\n- Inside the function, declare -n ref=\"$ref_name\" creates a nameref ref that points to\n  the variable named by $ref_name.\n- By setting ref=\"Hello from nameref!\", we indirectly update original_var.\n- Finally, we print original_var to see the updated value.\n\nWithout the nameref, you could achieve the same thing with this eval (read: evil) trick:\n\nThis achieves the same result. The eval \"$var_name=\\\"$new_value\\\"\" dynamically updates the\n$original_var variable through $var_name. However, eval can be risky for security, and\nthe nameref approach looks much cleaner syntactically.\n\nManaging multiple arrays\n\nNamerefs shine when you need to manage multiple arrays dynamically. Consider a scenario\nwhere you have several datasets stored in different arrays, and you want to process them\nusing a single function.\n\nThis returns:\n\nHere:\n\n- We declare three arrays: dataset1, dataset2, and dataset3.\n- The sum_array function takes the name of an array as an argument.\n- Using declare -n arr=\"$array_name\", we create a nameref arr that points to the\n  specified array.\n- We then iterate over the elements of arr to calculate the sum.\n- Finally, we call sum_array for each dataset, and the function correctly processes each\n  array based on the reference.\n\nWithout the nameref, you could again use the eval trick to achieve the same thing, but\nthis time it looks even uglier:\n\nThis approach is more complex, less secure, and harder to read in general. But the above\neval example was a bit contrived to make it look bad. You can achieve the same thing\nwithout eval or nameref in this particular case like this:\n\nHere, instead of passing the name of the dataset arrays as strings, we pass the elements of\nthe array to the function and add them. But I digress!\n\nAssociative arrays and nested references\n\nNamerefs also work with associative arrays and can be used for more complex data structures.\n\nIt prints:\n\nAnd voilĂ ! We have a function that can dynamically update the values in an associative\narray. This technique is useful for changing environments or contexts (staging/production)\nin shell scripts.\n\nIn this example:\n\n- We declare an associative array user_info containing user details.\n- The update_info function takes the name of the associative array, the key to update, and\n  the new value.\n- Using declare -n info=\"$info_name\", we create a nameref info pointing to user_info.\n- We update the specified key in the array.\n- Finally, we echo the updated user information.\n\nDoing this with eval isn't pretty. I'll leave that as an exercise for you if you like to\ntorment yourself.\n\nImplementing generic setter and getter functions\n\nBuilding on the earlier examples, you can use namerefs to create generic setter and getter\nfunctions, making it easier to manage configuration variables or environment settings in\nscripts.\n\nHere's an example:\n\nTo keep things simple, the env variable isn't a CLI argument. Based on whether env is\nset to staging or production, the script will print the relevant database values.\n\nFor staging, you'll see:\n\nFor production:\n\nOh, one extra thing: nameref was introduced in Bash 4.3, so you might run into problems if\nyou're using an ancient version like the one shipped with macOS.\n\n\n\n\n[man pages]:\n    https://www.gnu.org/software/bash/manual/bash.html#Shell-Builtin-Commands",
  "title": "Bash namerefs for dynamic variable referencing"
}