Find out where an environment variable was last set in bash - bash

Okay I know there is a bash debugger. But what I'm seeking is if I had an environment variable in one of my startup scripts and I don't know how it was set or where it might be, is there a way to find it other than exhaustively searching the scripts?
I mean is there a mechanism/tool that provides such a thing? Does bash keep track of variable setting locations?
Even though this might not seem very important but it crossed my mind the other day when I was helping a friend install OpenCL and the package supposedly set the variable $ATISTREAMSDKROOT automatically. Anyway the package was supposed to add a file to /etc/profile.d to allow for setting the variable, but it didn't. And luckily the variable came out blank.
But I was wondering if it hadn't come out blank, and the package added it to some random file, I would have probably had no way of telling where it is other than looking for it.
Of course I know one could write a sed command or two and search through the scripts but I'd consider that exhaustive search :D

One option would be to start an instance of bash with:
bash -x
... and look for where the variable is set in that output. To redirect that output to a file, you could do:
bash -x -ls -c "exit" 2> shell-startup-output
You should see in the output where each file is sourced.

Related

Add Path to OSX to El Capitan

I am trying to learn UNIX.
I am using a book called “Wicked Cool Shell Scripts”.
I am told that .bash_profile contains my login for bash and that I can add paths to it so that commands I enter in Terminal will find the scripts I am writing.
The contents of my current bash_profile is:
export PATH=~/bin:$PATH
When I type echo $PATH I get:
/usr/local/opt/php#7.0/bin:/Library/Frameworks/Python.framework/Versions/3.6/bin:/Library/Frameworks/Python.framework/Versions/3.5/bin:/Library/Frameworks/Python.framework/Versions/3.5/bin:/Library/Frameworks/Python.framework/Versions/3.3/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/opt/X11/bin
I would like to add a path so that - as the book suggests - I can write scripts and refer to them directly from the command line, instead of having to constantly navigate to that directory to run them.
I have a file with a shebang. It runs fine when I type its name and am in the same directory. I have moved that file to the folder scripts, which is located under crg/Users/ (ie: Users/crg/scripts)
According to this book, I can now alter my $PATH to include that directory, so that when I type that filename, the program will run.
I cannot do this successfully.
I don’t know why.
After every edit, I quit terminal and reopen it, to ensure it is using the newly edited bash_profile.
As per the books instructions on page 5, I have tried entering this in my bash_profile:
export PATH=”/Users/crg/scripts/:$PATH”
I save my bash_profile, quit Terminal, reopen it and type echo $Path
This is the result:
”/Users/crg/scripts/:??
This is not right. In fact, it's wildly wrong. And it does not allow me to run scripts from the folder indicated. It also seems to completely overwrite whatever was in the bash_profile before this, so I cannot - after doing this 'simple edit' suggested by a 'professional' - run a php -version command from the Terminal.
I am at a complete loss as to why this is happening.
Why is there a quotation mark at the beginning of this line (but not at the end)?
What's with the colon and the 2 question marks at the end of this line?
How do I add/append a path to my bash_profile?
More questions:
When I try and solve this on my own using “the Internet”, I discover many interesting versions of this ‘simple’ process: Here’s one suggested by a ‘professional’:
export PATH="${PATH}:/path/to/program/inside/package"
This is very different from what the book says...
Here’s another version of ‘how to do it’ by ‘a professional’:
export PATH=$PATH:/usr/local/sbin/modemZapp
Notice that this one doesn’t even have quotes. In both, the PATH variable comes before the actual path.
Why are there so many 'versions' of how to perform this simple task?
Can someone please tell me how to add a path to my .bash_profile?
UPDATE: I have followed the advice here, (add it to etc/paths) but this does not work either.
I get the exact same thing when I type echo $PATH in a new Terminal:
/usr/local/opt/php#7.0/bin:/Library/Frameworks/Python.framework/Versions/3.6/bin:/Library/Frameworks/Python.framework/Versions/3.5/bin:/Library/Frameworks/Python.framework/Versions/3.5/bin:/Library/Frameworks/Python.framework/Versions/3.3/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr:/usr/local/share/npm/bin:/Users/fhb/scripts:/opt/X11/bin
I can't help but note a comment on that last page: "I have been through at least a dozen different methods for adding directories to $PATH. Why are there so many and why do so few of them work?"
To answer your first question if you look closely your quotes are ” instead of ". I'm guessing you edited either your bash_profile or this post in a rich text editor instead of a plain text one. I would recommend notepad for Windows or nano for *nix if you are writing code. To fix this issue, replace the ” with ".
To answer your second question, bash is quite forgiving and will allow you to set a string variable in multiple different ways, even without quotes. However you can run into issues when a string contains whitespace, for example: /Users/lilHenry/my scripts.
The "${PATH}" syntax is just another way to declare a string. It has the benefit that it allows you to interpolate variables into a string like so:
prefix="foo"
echo "${prefix}bar"
This will output foobar, whereas echo "$prefixbar" will not output anything as the variable prefixbar has not been set. I would suggest sticking with the export PATH="/Users/me/bin:$PATH" syntax.

BASH variable in string

This script1 is not working as intended. I will explain below:
#!/bin/bash
### SETUP ###
USER="MYUSER"
DIRS="MYDIR"
BUCKET="mybucket"
DOACCESS="ACCESSKEY"
DOSECRET="SECRETKEY"
NAME="FILENAME"
EXPIRE="7 days"
NOW=$(date +"%d-%m-%Y")
DAY=$(date +"%a")
# ...lots of code that is working great...
### CLEAN OLD FILE FROM BUCKET ###
### This is the line that I am having issues with.
sh ./s3-del-old.sh ''"${BUCKET}"'/backup' '"${EXPIRE}"'
END
The script2 got copied from here.
What I had prior to following some instructions on Bash: Variable in single quote linked below was:
sh ./s3-del-old.sh "$BUCKET/backup" "$EXPIRE"
This did not work and was ignored when running the bash script.
I attempted to leave out the stuff that doesn't matter to the question below, although I believe I may have confused things. For this I apologize. Very simply, I have a line in script1 that calls another script2. I use variables to meet the needs of the script. To which it is not working and I cannot find a easy to understand solution online, thus the need to post the question.
----END OF UPDATE----
I have looked at some of the answered questions, but I am not finding a solution that fits my needs or one that I can understand fully to use for my needs.
I have tried following this, although I need a little more help.
This is what I am trying to do:
I have a backup script that uses DreamHost's DreamObjects to store my backups. The annoying part with DreamObjects is that it doesn't have any built in features for removing files created x days ago. Hence my problem. I would like to add a call to a bash file from my bash file. If that makes sense. :) If not, the code in question is below, you should be able to understand then.
I would really like to be able to add the code to my current script instead of using a separate file. I just don't know how to rewrite it properly without spending more time than I have on it. I found the code at.
My variables that matter for this problem:
BUCKET="mybucket"<br>
EXPIRE="7 days"
This is the line that calls the file:
sh ./s3-del-old.sh ''"${BUCKET}"'/backup' '"${EXPIRE}"'
This provides me with an error of date:
invalid date `-"${EXPIRE}"'
The file uses the following syntax to work:
s3-del-old "bucket" "30 days"
It does work perfectly when I use it in the command line on it's own, I just would like to add the call to one file so that I can use one cronjob instead of two. Plus, this way I can use the script with any of my domains/buckets by changing the variables. :)
The "other script" that you need to call is a bash script.
A bash script (usually) will not work if called as sh, as you are doing:
sh ./s3-del-old.sh ''"${BUCKET}"'/backup' '"${EXPIRE}"'
Please call the script with bash:
bash ./s3-del-old.sh "${BUCKET}"/backup "${EXPIRE}"
Or even better, let the script choose the shell that should run it:
./s3-del-old.sh "${BUCKET}"/backup "${EXPIRE}"
With the shebang of the file s3-del-old.sh:
#!/bin/bash
Sometimes I amaze myself at how difficult I try to make things...s3cmd has an expire function for files by create date...that will be a lot easier...
Really a big thank you to all that helped!
My bad this was suppose to be a comment not an answer. :)

Using variables between files in shell / bash scripting

This question has been posted here many times, but it never seems to answer my question.
I have two scripts. The first one contains one or multiple variables, the second script needs those variables. The second script also needs to be able to change the variables in the first script.
I'm not interested in sourcing (where the first script containing the variables runs the second script) or exporting (using environment variables). I just simply want to make sure that the second script can read and change (get and set) the variables available in the first script.
(PS. If I misunderstood how sourcing or exporting works, and it applies to my scenario, please let me know. I'm not completely closed to those methods, after what I've read, I just don't think those things will do what I want)
Environment variables are per process. One process can not modify the variables in another. What you're asking for is not possible.
The usual workaround for scripts is sourcing, which works by running both scripts in the same shell process, but you say you don't want to do that.
I've also given this some thought. I would use files as variables. For example in script 1 you use for writing variable values to files:
echo $varnum1 > /home/username/scriptdir/vars/varnum1
echo $varnum2 > /home/username/scriptdir/vars/varnum2
And in script 2 you use for reading values from files back into variables:
$varnum1=$(cat /home/username/scriptdir/vars/varnum1)
$varnum2=$(cat /home/username/scriptdir/vars/varnum2)
Both scripts can read or write to the variables at any given time. Theoretically two scripts can try to access the same file at the same time, I'm not sure what exactly would happen but since each file only contains one value, the time to read or write should be extremely short.
In order to even reduce those times you can use a ramdisk.
I think this is much better than scripts editing each other (yuk!). Live editing of scripts can mess up scripts and only works when you initiate the script again after the edit was made.
Good luck!
So after a long search on the web and a lot of trying, I finally found some kind of a solution. Actually, it's quite simple.
There are some prerequisites though.
The variable you want to set already has to exist in the file you're trying to set it in (I'm guessing the variable can be created as well when it doesn't exist yet, but that's not what I'm going for here).
The file you're trying to set the variable in has to exist (obviously. I'm guessing again this can be done as well, but again, not what I'm going for).
Write
sudo sed -i 's/^\(VARNAME=\).*/\1VALUE/' FILENAME
So i.e. setting the variable called Var1 to the value 5, in the file
test.ini:
sudo sed -i 's/^\(Var1=\).*/\15/' test.ini
Read
sudo grep -Po '(?<=VARNAME=).*' FILENAME
So i.e. reading the variable called Var1 from the file test.ini
sudo grep -Po '(?<=Var1=).*' test.ini
Just to be sure
I've noticed some issues when running the script that sets variables from a different folder than the one where your script is located.
To make sure this always go right, you can do one of two things:
sudo sed -i 's/^\(VARNAME=\).*/\1VALUE/' `dirname $0`/FILENAME
So basically, just put `dirname $0`/ (including the backticks) in front of the filename.
The other option is to make `dirname $0`/ a variable (again including the backticks), which would look like this.
my_dir=`dirname $0`
sudo sed -i 's/^\(VARNAME=\).*/\1VALUE/' $my_dir/FILENAME
So basically, if you've got a file named test.ini, which contains this line: Var1= (In my tests, the variable can start empty, and you will still be able to set it. Mileage may vary.), you will be able to set and get the value for Var1
I can confirm that this works (for me), but since you all, with way more experience in scripting then me, didn't come up with this, I'm guessing this is not a great way to do it.
Also, I couldn't tell you the first thing about what's happening in those commands above, I only know they work.
So if I'm doing something stupid, or if you can explain to me what's happening in the commands above, please let me know. I'm very curious to find out what you guys think if this solution.

Good way to arrange variables and command calls in bash scripts

Is there some good way to graphically align the command calls in bash scripts when they're prefixed with a variable? I've got a script with a lot of lines that look like this
GIT_EDITOR="some interesting command with 'quoting and' spaces" "${SOME_DIR}/actual_command" argument
this doesn't look good and it's hard to find what the command really is when scanning the source quickly.
This seems to be a bit better, but still not perfect:
GIT_EDITOR="some interesting command with 'quoting and' spaces" \
"${SOME_DIR}/actual_command" argument
Are there some more clear solutions?
Don't know if this is any better than what you suggested, but:
env \
GIT_EDITOR="some interesting command with 'quoting and' spaces" \
"$SOME_DIR/actual_command" argument
You could arrange this way:
ShortVarName="some interesting command with 'quoting and' spaces"
GIT_EDITOR="$ShortVarName" "${SOME_DIR}/actual_command" argument
The idea is to keep the injected environment part as short as possible. If you name ShortVarName in a descriptive way, it will be readable too.
If you have lots of ShortVarName things, you could group them at the top of the file somewhere, so you can reference them quickly (using your search function?) if needed, but so they don't get in the way of the readability of the code where they are actually used.
If the GIT_EDITOR variable stays unchanged in your script, and it's OK to have it globally available, you may want to export it once at the beginning and not prepend it to commands at all.

Converting a history command into a shell script

This is sort of one of those things that I figured a lot of people would use a lot, but I can't seem to find any people who have written about this sort of thing.
I find that a lot of times I do a lot of iteration on a command-line one-liner and when I end up using it a lot, or anticipate wanting to use it in the future, or when it becomes cumbersome to work with in one line, it generally is a good idea to turn the one-liner into a shell script and stick it somewhere reasonable and easily accessible like ~/bin.
It's obviously too cumbersome to use any sort of roundabout method involving a text editor to get this done, and it's possible to simply do it on the shell, for instance in zsh typing
echo "#!/usr/bin/env sh" > ~/bin/command_from_history_number_523.sh && echo !523 >> ~/bin/command_from_history_number_523.sh
followed by pressing Tab to inject the !523rd command and somehow shoehorning it into an acceptable string to be saved.
This is particularly cumbersome and has at minimum three problems:
Does not work in bash as it does not complete the !523
Requires some manual inspection and string escapement
Requires too much typing such as the script name must be entered twice
So it looks like I need to do some meta shell scripting here.
I think a good solution would function under both bash and zsh, and it should probably work by taking two arguments, an integer for the history command number and a name for the shell script to poop out in a hardcoded directory which contains that one command. Furthermore, under bash, it appears that multi-line commands are treated as separate commands, but I'm willing to assume that we only care about one-liners here and I only use zsh anyway at this point.
The stumbling block here is that i think I'll still be running shell scripts through bash even when using zsh, so it won't likely then be able to parse zsh's history files. I may need to make this into two separate programs then.
Update: I agree with #Floris 's comment that direct use of the commands like !! would be helpful though I am not sure how to make this work. Suppose I have the usage be
mkscript command_number_24 !24
this is inadequate because mkscript will be receiving the expanded out contents of the 24th command. if the 24th command contains any file globs or somesuch they will have been expanded already. This is bad, and I basically want the contents of the history file, i.e. the raw command string. I guess this can be worked around by manually implementing those shortcuts in here. Or just screw it and just take an integer argument.
function mkscript() {
echo '#!/bin/bash' > ~/bin/$2
history -p '!'$1 >> ~/bin/$2
}
Only tested in Bash.
Update from OP: In zsh I can accomplish this with fc -l $2 $2

Resources