Here is my function so far:
function create {
a=$3
while [ $a -lt $secondnum ]
do
echo "mkdir $1/$a"
if [ $2 -lt $firstnum ]
then
sum=$(($2+1))
d=$(($a))
create "$1/$d" $sum 0
fi
a=$(($a+1))
done
}
Its echoing
mkdir hw1/0
mkdir hw1/0/0
mkdir hw1/0/0/0
mkdir hw1/0/0/1
mkdir hw1/0/0/2
It's supposed to create a whole directory, not just one branch. (e.g. hw///* branch aswell) This is a homework project so I need to use bash.
I believe bash variables are global by default. If you want a function-local variable, you need to use the local keyword.
$ help local
local: local [option] name[=value] ...
Define local variables.
Create a local variable called NAME, and give it VALUE. OPTION can
be any option accepted by 'declare'.
Local variables can only be used within a function; they are visible
only to the function where they are defined and its children.
Exit Status:
Returns success unless an invalid option is supplied, an error occurs,
or the shell is not executing a function.
Related
I'm currently stuck on how to do the following:
I have a settings file that looks like this:
USER_ID=12
ROLE=admin
STARTED=10/20/2010
...
I need to access the role and map the role to one of the variables in the script below. After I will use that variable to call open a doc with the correct name.
test.sh
#!/bin/sh
ADMIN=master_doc
STAFF=staff_doc
GUEST=welcome_doc
echo "accessing role type"
cd /setting
#open `settings` file to access role?
#call correct service
#chmod 555 master_doc.service
Is there a way to interpolate strings using bash like there is in javascript? Also, I'm guessing I would need to traverse through the settings file to access role?
With bash and grep and assuming that the settings file has exactly one line beginning with ROLE=:
#!/bin/bash
admin=master_doc
staff=staff_doc
guest=welcome_doc
cd /setting || exit
role=$(grep '^ROLE=' settings)
role=${role#*=}
echo chmod 555 "${!role}.service"
Drop the echo after making sure it works as intended.
Look into Shell Parameter Expansion for indirect expansion.
From what I understand, you want to get the variables from settings, use $role as an indirect reference to $admin, i.e. master_doc, then turn that into a string, master_doc.service.
Firstly, instead of indirection, I recommend an associative array since it's cleaner.
You can use source to get variables from another file, as well as functions and other stuff.
Lastly, to dereference a variable, you need to use the dollar sign, like $role. Variable references are expanded inside double-quotes, so that's sort of the equivalent of string interpolation.
#!/bin/bash
# Associative array with doc names
declare -A docs=(
[admin]=master_doc
[staff]=staff_doc
[guest]=welcome_doc
)
echo "accessing role type"
cd setting || exit
source settings # Import variables
true "${ROLE?}" # Exit if unset
echo chmod 555 "${docs[$ROLE]}.service" # Select from associative array
# ^ Using "echo" to test. Remove if the script works properly.
You can source the settings file to load the variables:
source settings
And then you can use them in your script:
chmod 555 "${admin}.service" # will replace ${admin} with master_doc
I'd certainly use source(.)
#!/bin/sh
ADMIN=master_doc
STAFF=staff_doc
GUEST=welcome_doc
echo "accessing role type"
. /setting/settings 2> /dev/null || { echo 'Settings file not found!'; exit 1; }
role=${ROLE^^} # upercase rolename
echo ${!role}.service # test echo
My project (port from ksh) use some directories as autoloadable functions.
In those directories each filenames as the name of a function declared inside the file, sourcing that file to declare (implement) the function. Each directories could be considered a 'package' that augment the bash builtin set via functions. I have about 20 packages, and the number of functions per package can be significant (can reach 30 in some packages).
The bash documentation includes an example implementation of autoloading:
https://www.apt-browse.org/browse/ubuntu/trusty/main/all/bash-doc/4.3-6ubuntu1/file/usr/share/doc/bash/examples/functions/autoload.v2
However, that implementation requires the set of potentially-autoloadable functions to be known (and enumerated) at shell startup time.
Is an implementation that doesn't have that limitation possible?
Well, after asking, my turn to 'give' :) to the SO community. I investigated this auto load feature I need and came up with 2 implementations, I provide one them here, so some may suggest enhancements or point out bugs. I'll post the second one on a second post.
The 2 implementations will runs some test cases, so before presenting the implementations I present the common test cases. We have 2 directories a1/ and a2/ that host function definitions located in file with same name as the function, each dir could be considered a 'package' dir containing functions for this package, and then function in there are namespaced with the package name (dir name), with few exception for the test purpose.
./a1/ac_f3::
function ac_f3
{ echo "In a1 ac_f3() : args=$#"
}
./a1/a1_f1::
function a1_f1
{ echo "In a1_f1() : args=$#"
}
./a1/a1_f2::
function a1_f22
{ echo "In a1_f2() : args=$#"
}
./a2/ac_f3::
function f3
{ echo "In a2 ac_f3() : args=$#"
}
./a2/a2_f1::
function a2_f1
{ echo "In a2_f1() : args=$#"
}
ac_f3 is a function that is not namespaced, and then common to both dir a1/ a2/ yet with different implementation, this is to demonstrate the $FPATH precedence.
a1_f2 is a bogus one it doesn't implement the function a1_f2() and then we must fail gracefully.
a1_f1, a2_f1, simply implement a1_f1() a2_f2(), and must be found and executed.
command_not_found_handle implementation
Thanks Charles for bringing the command_not_found_handle option, because, surely auto loadable function are related to the fact that a 'command' has not been found and then we try to find a auto loadable to load and execute.
But amazingly, the bash shell has an interesting "feature", i.e some undocumented behavior.
Bash doc says.
If the search is unsuccessful, the shell searches for a defined
shell function named command_not_found_handle. If that
function exists, it is invoked with the original command and
the original command's arguments as its arguments, and the
function's exit status becomes the exit status of the shell.
If that function is not defined, the shell prints an error
message and returns an exit status of 127.
This is misleading because here we talk about the command_not_found_handle() function invocation, and then we may infere 'from the shell context' and this is not the case.
In the shell logic, we failed to get an alias, then fail to get a function, then failed to get an 'external to the shell' program, and the shell is already in a sub-shell creation mode, so command_not_found_handle() is invoked but in a subshell. not the shell context. This could be OK, but the 'funny feature' here is that the sub-process created is not clean, its $$ and $PPID are not set correctly, may be this will be fixed one day. To exhibit this bash feature we can do
function command_not_found_handle
{ echo $$ ; sh -c 'echo $PPID'
}
PW$ # In a shell context invocation
PW$ command_not_found_handle
2746
2746
PW$ # In a subshell invocation (via command not found)
PW$ qqq
2746
3090
Back to our autoload feature, this mean we want to install more functions in a shell instance, nothing that can be done in a subshell, so basically command_not_found_handle() is of tiny help and can do nothing beside signal its parent we got entered (then a command was not found), we will exploit this feature in our implementation.
# autoload
# This file must be sourced
# - From your rc files if you need autoloadable fuctions from your
# interactive shell
# - From any script that need autoloadable functions.
#
# The FPATH must be set with a set of dirs/ where to look to find
# file name match the function name to source and execute.
#
# Note that if FPATH is exported, this is a way to export functions to
# script subshells
# Create a default command_not_found_handle if none exist
declare -F command_not_found_handle >/dev/null ||
function command_not_found_handle { ! echo bash: $1 command not found>&2; }
# Rename current command_not_found_handle
_cnf_body=$(declare -f command_not_found_handle | tail -n +2)
eval "function _cnf_prev $_cnf_body"
# Change USR1 to your liking
CNF_SIG=USR1
function autoload
{ declare f=$1 ; shift
declare d s
for d in $(IFS=:; echo $FPATH)
do s=$d/$f
[ -f $s -a -r $s ] &&
{ . $s
declare -F $f >/dev/null ||
{ echo "$s exist but don't define $f" >&2 ; return 127
}
$f "$#" ; return
}
done
_cnf_prev $f "$#"
}
trap 'autoload ${BASH_COMMAND[#]}' $CNF_SIG
function command_not_found_handle
{ kill -$CNF_SIG $$
}
WARNING, if you ever use this 'autoload' file be prepared for bash fix, it may one day reflect the real $$ $PPID, in which case you will need to fix the above snippet with
$PPID instead of $$.
Results.
PW$ . /path/to/autoload
PW$ FPATH=a1:a2
PW$ a1_f1 11a 11b 11c
In a1_f1() : args=11a 11b 11c
PW$ a2_f1 21a 21b 21c
In a2_f1() : args=21a 21b 21c
PW$ a1_f2 12a 12b 12c
a1/a1_f2 exist but don't define a1_f2
PW$ ac_f3 c3a c3b c3c
In a1 ac_f3() : args=c3a c3b c3c
PW$ qqq
Command 'qqq' not found, did you mean:
command 'qrq' from snap qrq (0.3.1)
command 'qrq' from deb qrq
See 'snap info <snapname>' for additional versions.
What we got here is correct, a1_f1() a2_f1() are found, loaded, executed.
a1_f2() is nowhere to be found, despite having a file that could host it.
The qqq invocation display the chaining of the handlers, going function autoload fist, then the ubuntu command-not-found package (if installed) meaning we are not loosing the command_not_found_handle() user experience.
Note there is no 'admin' functions here like adding/removing/reloading functions.
Adding is a matter of setting file in dirs present in $FPATH
Removing is a matter of removing the source file and unset -f the function
Reloading is a matter of editing the source file and unset -f the function.
Reloading function can be pretty neat during development in interactive shell, but all this can be done with a simple
unset -f funcname, so basically you edit your source file. unset the function, then call it, you get the latest. Same may happen in a script daemon, one could implement a signal to the daemon and the trap handler would simply unset a set of functions that would then be reloaded without stopping/restarting the daemon.
Another feature here is that shell 'package' are possible, i.e a source file may implement 'many' functions, some are the external API, other are internal to the package, since all is flat in the shell, function are namespaced, and then each external API functions (albeit documented) can be hard linked to the same file. The first external API used will load all the package functions.
In my project, the documentation is extracted from the packages sources, and then hardlink are inferred and build at this time.
PROs and CONs
PROs
Here we got a light signature in the autoload sourcing, i.e from scripts or from bash rc file (interactive), the define of the autoload() is modest.
It is very dynamic, in the sense that function loading and executing is really deferred until really needed.
CONs
It grabs a signal number, that would not be necessary should the command_not_found_handle() be a real function called from the shell context, this could happen one day.
It is implemented on a bash feature that may move (wrong, $$ $PPID) then need maintenance on the moving target.
Conclusion
This implementation is OK for me (I Don't care loosing SIGUSR1). The ideal solution would be that command_not_found_handle() would be cleanly implemented and then called in the shell context. The a similar implementation would be possible without any signal.
This is a second implementation to avoid the signal usage seen in the previous implementation and the usage of the command_not_found_handle() that seems not completly stable.
autoload::
function autoload
{ local d="$1" && [ "$1" ] && shift && autoload "$#"
local identifier='^[_a-zA-Z][_a-zA-Z0-9]*$'
[ -d "$d" -a -x "$d" ] && cd "$d" &&
{ for f in *
do [[ $f =~ $identifier ]] && alias $f=". $PWD/$f;unalias $f;$f"
done
cd ->/dev/null 2>&1
}
}
autoload $# $(IFS=:; echo $FPATH)
Here again we got to source this autolaod file either in rc file or in scripts.
The usage of FPATH is not really needed (see Notes for more details on FPATH)
So basically the idea is to source the autoload file along with a set of directories to look for.
PW$ . /path/to/autoload a1 a2
PW$ alias | grep 'a[12c]_*'
alias a1_f1='. /home/phi/a1/a1_f1;unalias a1_f1;a1_f1'
alias a1_f2='. /home/phi/a1/a1_f2;unalias a1_f2;a1_f2'
alias a2_f1='. /home/phi/a2/a2_f1;unalias a2_f1;a2_f1'
alias ac_f3='. /home/phi/a1/ac_f3;unalias ac_f3;ac_f3'
PW$ declare -F | grep 'a[12c]_*'
After the autoload sourcing, we got all the alias defined and no functions.
This is a bit heavier than the previous implementation, yet pretty lightweight, alias are not costly to create in the shell, even with hundred of them.
PW$ a1_f1 11a 11b 11c
In a1_f1() : args=11a 11b 11c
PW$ a2_f1 21a 21b 21c
In a2_f1() : args=21a 21b 21c
PW$ alias | grep 'a[12c]_*'
alias a1_f2='. /home/phi/a1/a1_f2;unalias a1_f2;a1_f2'
alias ac_f3='. /home/phi/a1/ac_f3;unalias ac_f3;ac_f3'
PW$ declare -F | grep 'a[12c]_*'
declare -f a1_f1
declare -f a2_f1
Here we see that a1_f1() and a2_f2() are then loaded and executed, they are removed from the alias list and added in the function list.
PW$ a1_f2 12a 12b 12c
a1_f2: command not found
PW$ ac_f3 c3a c3b c3c
In a1 ac_f3() : args=c3a c3b c3c
PW$ qqq
Command 'qqq' not found, did you mean:
command 'qrq' from snap qrq (0.3.1)
command 'qrq' from deb qrq
See 'snap info <snapname>' for additional versions.
Here we see that a1_f2() is not found, not well reported as in the previous implementation.
ac_f3() is the one from a1/ as expected.
qqq still provide the command-not-found distro package result if installed ( normal we didn't mess with command_not_found_handle() )
PROs and CONs
PROs
Not sitting on a bash bug, i.e could live for a while after bash updates.
CONs
A little bit heavier than previous implementation, yet acceptable.
Much simpler, well may be not simpler, but surely shorter than proposed examples in bash documentation, and a bit more lazy, i.e function are loaded only when necessary (not the aliases though)
Multi function 'package' files along with hardlink for external API exposure is less performing, because each external API function (hardlink) will trig a reload of the file, unless the package file is well written removing all the excess aliases after loading.
I would like to setup some files for development, staging and production with environment variables, for example:
application_root/development.env
KEY1=value1
KEY2=value2
There would be similar files staging.env and production.env.
I am looking for a couple different bash scripts which would allow the loading of all these variables in either development or staging/production.
In local development I want to effectively run export KEY1=value1 for each line in the file.
For staging/production I will be deploying to Heroku and would like to effectively run heroku config:set -a herokuappname KEY1=value1 for each line in the staging or production.env files.
I know there are some gems designed for doing this but it seems like this might be pretty simple. I also like the flexibility of having the .env files as simple lists of keys and values and not specifically being tied to any language/framework. If I would have to change something about the way these variables need to be loaded it would be a matter of changing the script but not the .env files.
In the simplest form, you can load the key-value pairs into a bash array as follows:
IFS=$'\n' read -d '' -ra nameValuePairs < ./development.env
In Bash v4+, it's even simpler:
readarray -t nameValuePairs < ./development.env
You can then pass the resulting "${nameValuePairs[#]}" array to commands such as export or heroku config:set ...; e.g.:
export "${nameValuePairs[#]}"
Note, however, that the above only works as intended if the input *.env file meets all of the following criteria:
the keys are syntactically valid shell variable names and the lines have the form <key>=<value>, with no whitespace around =
the lines contain no quoting and no leading or trailing whitespace
there are no empty/blank lines or comment lines in the file.
the values are confined to a single line each.
A different approach is needed with files that do not adhere to this strict format; for instance, this related question deals with files that may contain quoted values.
Below is the source code for a bash script named load_env (the .sh suffix is generally not necessary and ambiguous):
You'd invoke it with the *.env file of interest, and it would perform the appropriate action (running heroku config:set … or export) based on the filename.
However, as stated, you must source the script (using source or its effective bash alias, .) in order to create environment variables (export) visible to the current shell.
To prevent obscure failures, the script complains if you pass a development.env file and have invoked the script without sourcing.
Examples:
./load_env ./staging.dev
. ./load_env ./development.dev # !! Note the need to source
load_env source code
#!/usr/bin/env bash
# Helper function that keeps its aux. variables localized.
# Note that the function itself remains defined after sourced invocation, however.
configOrExport() {
local envFile=$1 doConfig=0 doExport=0 appName
case "$(basename "$envFile" '.env')" in
staging)
doConfig=1
# Set the desired app name here.
appName=stagingapp
;;
production)
doConfig=1
# Set the desired app name here.
appName=productionapp
;;
development)
doExport=1
;;
*)
echo "ERROR: Invalid or missing *.env file name: $(basename "$envFile" '.env')" >&2; exit 2
esac
# Make sure the file exists and is readable.
[[ -r "$envFile" ]] || { echo "ERROR: *.env file not found or not readable: $envFile" >&2; exit 2; }
# If variables must be exported, make sure the script is being sourced.
[[ $doExport -eq 1 && $0 == "$BASH_SOURCE" ]] && { echo "ERROR: To define environment variables, you must *source* this script." >&2; exit 2; }
# Read all key-value pairs from the *.env file into an array.
# Note: This assumes that:
# - the keys are syntactically valid shell variable names
# - the lines contain no quoting and no leading or trailing whitespace
# - there are no empty/blank lines or comment lines in the file.
IFS=$'\n' read -d '' -ra nameValuePairs < "$envFile"
# Run configuration command.
(( doConfig )) && { heroku config:set -a "$appName" "${nameValuePairs[#]}" || exit; }
# Export variables (define as environment variables).
(( doExport )) && { export "${nameValuePairs[#]}" || exit; }
}
# Invoke the helper function.
configOrExport "$#"
I want to make this variable
local to="$HOME/root_install/grunt"
be available to the entire file
makeGrunt(){
# set paths
local to="$HOME/root_install/grunt"
cd $to
sudo npm install -g grunt-init
sudo git clone https://github.com/gruntjs/grunt-init-gruntfile.git ~/.grunt-init/gruntfile
sudo grunt-init gruntfile
}
In POSIX-like shells - unless you use nonstandard constructs such as local, typeset, or declare - variables created implicitly through
assignment have global scope in the shell at hand.
Thus, to="$HOME/root_install/grunt" will make variable $to available anywhere in the current shell - unless you're inside a function and that variable was explicitly marked as local.
andlrc's helpful answer demonstrates the pitfalls associated with subshells - subshells are child processes that are clones of the original shell - they see the same state, but cannot modify the original shell's environment.
Bash shells use dynamic
scopes
which means that all variables are available for all called functions, commands,
etc. Consider this:
var=1
a() {
local var=2
b
}
b() {
echo "$var"
}
a # 2
b # 1
a # 2
When using the local keyword a variable will be available for the function, in
where it's defined, but also within all functions called from that function.
The same applies when a variable is created without the local keyword. With
that exception that it will also be available outside the function.
One more thing to be aware of is that whenever a subshell is created a variable
will not be able to "leave" it, i.e. when a pipe is involved. Consider this:
sum=0
seq 3 | while read -r num; do
sum=$((sum + num))
echo "$sum" # will print 1, 3 and 6
done
echo "$sum" # 0 huh? 1 + 2 + 3 = 0?
#!/bin/bash
if[$LD_PATH == ""]
then
export LD_PATH=$1
else
export LD_PATH=$LD_PATH
fi
#excute the program that needs the path
./someProgThatNeedsThePath
i keep getting "cannot open shared object file"
First, your bash syntax is broken due to lack of spaces. First, you would need spaces around the [ and ] closures. If you use the newer alternate double bracket syntax, you don't have to worry about quoting variables you are testing.
#!/bin/bash
if [[ $LD_PATH = "" ]]; then
export LD_PATH="$1"
else
export LD_PATH="$LD_PATH"
fi
But your real problem is not that at all. Since your code is running in a separate bash shell, the code will have no effect on anything you run in the parent shell. In order to do that you would want to build a function:
function add_to_ld_path () {
if [[ -z $LD_PATH ]]; then
export LD_PATH="$1"
fi
}
Add that code to your .profile, then run it with add_to_ld_path /my/path when you want to use it. Note that since your "else" statement wasn't actually doing anything, I removed it. I also replaced your test for a blank string by making your own with quotes with the builtin empty string test.
Next up is what you are actually trying to accomplish. If all you want to do is set the variable if it's empty, UncleAli's solution is very simple. But it might be useful to do something like this:
function add_to_ld_path () {
case ":$LD_PATH:" in
*"$1"*) :;;
*) LD_PATH=$LD_PATH:$1
esac
}
This would check the current path and add the path given if it wasn't already part of the LD_PATH variable, otherwise it would leave it alone.
if [ -z "$LD_PATH" ]
then
export LD_PATH=$1
fi
Will that do the job?