Variables that work everywhere - bash

How can I set variables that work everywhere? My .bashrc has:
Questions='/Users/User/Stackoverflow/Questions'
Address='My address'
My file has:
$Questions
$Addres
The command "$ cat my_file" in Shell prints "$Questions" and "$Address", instead of "/Users/User/Stackoverflow/Questions" and "My address". Other places where I would like have the variables are Email and Browser. But I cannot get them working.
How can I have my variables to work in every program?

cat doesn't interpret variables. It simply prints out the contents of a file, byte-for-byte.
If you want to be able to print out the values of the variables with my_file, I would suggest changing it to read
echo "$Questions"
echo "$Address"
Then you can "source" it (kind of like running it in the current shell environment) with
$ source my_file
or
$ . my_file
You might also have to use export in .bashrc, like so:
export Questions='/Users/User/Stackoverflow/Questions'
export Address='My address'

How can I have my variables to work in every program?
You can't. Bash and cat are two separate programs. You set a bash variable, it doesn't mean that cat will behave like bash to interpret it. $VARNAME is a shell syntax, cat is a different program, that share almost nothing with the shell.
You can export the shell variable as an environment variable, but cat is not programmed to replace any text templates, it goes way beyond its purpose.
Instead, you may use sed to perform text template substitutions:
sed -e "s|#QUESTIONS#|$Questions|g; s|#ADDRESS#|$Address|g" file.txt
This will replace all instances of #QUESTIONS# and #ANSWERS# in the file with the contents of $Questions and $Address shell variables. Note that these shell variables must not contain any pipe ("|") symbols, since the suggestion uses them as delimiters.

You can get the "cat" thing to work using some nasty hackery:
eval "$(printf 'cat << END\n%s\nEND' "$(< foo)")"
Where "foo" is the file that contains your text you want the bash parameters expanded from. This solution basically just converts the text into a here document, which does expand bash parameters.
cat <<END
[your text]
END
Limitations:
You can't have a line with just "END" in the text file or the solution will break. It'll think the line with "END" in the text file ends the here document instead of the END in the printf command, and the ouput will end early.
TBH:
This is something you just shouldn't want to do. If you want to make template files, go find a templating system that's built for this. You shouldn't be raping bash into doing something that it isn't built to do. It's a scripting language, not a templating system. It's built to parse scripts with a well defined syntax, not arbitrary text files.

You can't. In order to do that, you'd need the cooperation of every email client writer, every browser writer, every utility writer.

Type env see if the variables exist.
If so, try calling them with ${VAR_NAME}.
After re-iterating I now see your problem. You just can't have a text file, cat out it's content and expect the environments variables to be parsed. You just can't.
If it was a bash script file, and you were to run it, you could echo out the variables and they would be parsed like you want.

From your comment on Paul Tomblin's answer:
Are you sure? I have heard that you can have all kind of things like RSS-reader in Emacs. Perhaps, there is a better cooperation. I am a VIM-user, so I don't know. If someone knows whether Emacs has great cooperation between programs, please do not hesitate to share your knowledge. – UnixBasics (3 mins ago)
Do you want (or would you be satisfied with) an editor that can expand your environment variables?
That is a different (i.e. simpler, and possible) problem.
On purely theoretical level, I suppose a hacked filesystem could do what you want, but it would certainly break all kinds of binary storage. So we add a filesystem flag for text (expandable) and non-text file types. And we'd need a way for the filesystem to know what set of variable to expand, and ad nauseum...

Just add to your ~/.profile or to ~/.bashrc:
export Questions='/Users/User/Stackoverflow/Questions'
export Address='My address'

Related

Use a variable on a script command line when its value isn't set until after the script starts

How to correctly pass to the script and substitute a variable that is already defined there?
My script test.sh:
#!/bin/bash
TARGETARCH=amd64
echo $1
When I enter:
bash test.sh https://example/$TARGETARCH
I want to see
https://example/amd64
but I actually see
https://example/
What am I doing wrong?
The first problem with the original approach is that the $TARGETARCH is removed by your calling shell before your script is ever invoked. To prevent that, you need to use quotes:
./yourscript 'https://example.com/$TARGETARCH'
The second problem is that parameter expansions only happen in code, not in data. This is, from a security perspective, a Very Good Thing -- if data were silently treated as code it would be impossible to write secure scripts handling untrusted data -- but it does mean you need to do some more work. The easy thing, in this case, is to export your variable and use GNU envsubst, as long as your operating system provides it:
#!/bin/bash
export TARGETARCH=amd64
substitutedValue=$(envsubst <<<"$1")
echo "Original value was: $1"
echo "Substituted value is: $substitutedValue"
See the above running in an online sandbox at https://replit.com/#CharlesDuffy2/EcstaticAfraidComputeranimation#replit.nix
Note the use of yourscript instead of test.sh here -- using .sh file extensions, especially for bash scripts as opposed to sh scripts, is an antipattern; the essay at https://www.talisman.org/~erlkonig/documents/commandname-extensions-considered-harmful/ has been linked by the #bash IRC channel on this topic for over a decade.
For similar reasons, changing bash yourscript to ./yourscript lets the #!/usr/bin/env bash line select an interpreter, so you aren't repeating the "bash" name in multiple places, leading to the risk of those places getting out of sync with each other.

How do you use file lists (.xcfilelist) within Xcode 10 script build phases?

Starting with Xcode 10, build script phases can use file lists (.xcfilelist) for input and output instead of specifying input/output files directly. Those files seem to support comments (the WWDC sample showed command line comments on top), blank lines (also in the sample), and otherwise expect one file path per line. If these file contain build settings (e.g. $(SRCROOT)), these are expanded prior to calling the script, just like they would have been expanded if the file path was directly given as input/output file.
This sounds like a great feature but how would you use these file lists in your actual script?
When specifying the files directly, you had the shell variables SCRIPT_INPUT_FILE_COUNT and SCRIPT_OUTPUT_FILE_COUNT and then one variable for each input/output file, named SCRIPT_INPUT_FILE_# and SCRIPT_OUTPUT_FILE_# where # used to be an up-counting number. Assuming that you have an equal number of input/output file, this script would print them all:
#!/bin/sh
: $((i=0))
while [ $i -lt "$SCRIPT_INPUT_FILE_COUNT" ]
do
eval fileIn="\$SCRIPT_INPUT_FILE_${i}"
eval fileOut="\$SCRIPT_OUTPUT_FILE_${i}"
echo "$fileIn --> $fileOut"
: $((i=i+1))
done
This is a clean POSIX compatible shell script, yes, you can make it even nicer when requiring bash but the code above should work with every sh compatible shell (which it also promisses when using #!/bin/sh and not #!/bin/bash).
But when using file lists, SCRIPT_INPUT_FILE_COUNT is 0. Instead you get SCRIPT_INPUT_FILE_LIST_COUNT and SCRIPT_OUTPUT_FILE_LIST_COUNT, and the variables SCRIPT_INPUT_FILE_LIST_# and SCRIPT_OUTPUT_FILE_LIST_#, containing the paths to the pre-processed file lists, where all comments and blank lines have been stripped and all build settings have already been expanded.
Now, how would I go about using these file lists in my script? How would the tiny sample script above produce the same output using file lists in Xcode? I'm not really good at shell scripting and I'm looking for a clean solution that doesn't require any other script interpreter but sh.
This will dynamically construct the SCRIPT_INPUT_FILE_LIST_0, SCRIPT_INPUT_FILE_LIST_1, etc. values and access them from the environment vars passed to the script by Xcode. Swap out the echo "${file_path}" line if you want to do something other than printing each of the lines from the xcfilelist(s).
#!/usr/bin/env bash
for index in $(seq $SCRIPT_INPUT_FILE_LIST_COUNT); do
# 1 => `SCRIPT_INPUT_FILE_LIST_0`
filelist=SCRIPT_INPUT_FILE_LIST_$((index-1))
# `SCRIPT_INPUT_FILE_LIST_0` => value in $SCRIPT_INPUT_FILE_LIST_0
filelist_path=${!filelist}
while read -r file_path; do
echo "${file_path}"
done <$filelist_path
done
I'm not aware of a way to get access to the file list itself inside the shellscript. However, the idea of a file list is you ideally have one for however many files there are. So, I usually hardcode it to the same value I gave xcode. It's a bit duplicated, but not a whole lot:
set -e
while read file; do
EXPANDED=`eval echo "$file"`
echo "do something with $EXPANDED"
done <"${SRCROOT}/path/to/files.xcfileslist"
As a side effect of this read device we strip whitespace and do some other light processing. If you are particular about how you want this to happen, see this SO answer.
I believe most (all?) build settings are exported into the script as environment variables. So by evaluating them with eval here we expand them.
Use of eval opens up the possibility that a malicious file list can execute code. Then again it's probably located in the same place as the build script you're executing so I'm not sure it's a very practical problem. Other shells have more secure ways of going about this, but I'm not aware of any for vanilla sh and default macOS.

How to pass multiple variables in shell script

I have a shell script that I'm trying to write to a file using multiple variables, but one of them is being ignored.
#!/bin/bash
dir=/folder
name=bob
date=`date +%Y`
command > $dir/$name_$date.ext
The $name is being ignored. How can I fix this?
Have you noticed that the _ was "ignored" as well? That's a big hint.
If you use set -u, you'll see the following:
-bash: name_: unbound variable
The way bash parses it, the underscore is part of the variable name.
There are several ways to fix the problem.
The cleanest is the ${var} construct which separate the variable name from its surroundings.
You can also use quotation in various ways to force the right parsing, e.g.: "$dir/$name""_$date.ext"
And in case your variables might contain spaces (now, or in the future) use quotation for words.
command >"$dir/${name}_$date.ext"
command >"${dir}/${name}_${date}.ext"
Both these are fine, just pick one style and stick to it.

Why do scripts define common commands in variables?

I see this all the time at my place of work:
#!/bin/sh
.....
CAT=/usr/bin/cat # An alias for cat
MAIL=/usr/bin/mail # An alias for mail
WC=/usr/bin/wc # An alias for word count
GREP=/usr/bin/grep # An alias for grep
DIRNAME=/usr/bin/dirname # An alias for dirname
RM=/usr/bin/rm # An alias for rm
MV=/usr/bin/mv # An alias for mv
.....
Is it just my company that does this? Is there a reason why you would want to spell out where these extremely common commands are? Why would I want $CAT to refer to /usr/bin/cat when cat already refers to /usr/bin/cat? Am I missing something? It seems like its needlessly redundant.
Using the full pathname ensures that the script operates correctly even if it's run by a user who customizes their PATH environment variable so that it finds different versions of these commands than the script expects.
Using variables simplifies writing the script, so you don't have to write the full pathname of a command each time it appears in the script.
Is it just my company that does this?
No.
Is there a reason why you would want to spell out where these extremely common commands are?
Yes.
Why would I want $CAT to refer to /usr/bin/cat when cat already refers to /usr/bin/cat?
Are you sure cat always refers to /usr/bin/cat? What if your script happens to be run in an environment where there is a different cat earlier in the path? Or where there is simply a user-controlled directory earlier in the path, where a user could install a rogue cat command? If your script ever happens to be run with elevated privileges, then do you really want to give random users the ability to do anything they want to your system?
Are you sure cat is supposed always to refer to /usr/bin/cat? If ever the script were installed in an environment where a different cat were needed (say /usr/local/bin/gnucat), then would you prefer to modify one line or twenty?
Am I missing something? It seems like its needlessly redundant.
Yes, you are missing something.
One would like to avoid writing out /usr/bin/cat everywhere they want to run cat, and one would like to be able to choose a different cat where needed (or more likely a different make or grep or sed). On the other hand, one wants to avoid potentially unsafe external influence on the behavior of a trusted script. Defining the full path to the command in a shell variable and then using that variable to run the command accomplishes these objectives.
One way to avoid this and still have the safety of ignoring the user's environment is to explicitly spell out the variables in the script
#!/bin/sh
PATH=/bin:/usr/bin # maybe you need something in /usr/sbin, add that
LC_ALL=C # ignore the user's locale
LD_LIBRARY_PATH=something # or unset it if you want nothing
# then
cat /a/file # have confidence you're using /bin/cat
There may well be others: check the man pages of the programs you use in your code.
Welcome to the enterprise where nothing is taken for granted.
These are commonly defined to ensure correct version, or to enforce env setup across many boxes.
https://github.com/torvalds/linux/blob/master/tools/scripts/Makefile.include#L15
This way you can check if dir exist.

Shell command to get config values

I am using the sh 3.2 in Mac Os X. I have a file test.conf
config1="Configuration 1"
config2="a lot of text"
config3=...
So I only need to get the config1= and config2= parameter. How can I set a variable, that I can do this:
> echo $variable
Configuration 1
So simple, but I am not doing it work.
the sommand you are looking for is source
source test.conf
echo $config1 #echoes Configuration 1
if you need to have config1 in variable, add
varible=$config1
At a rough guess...
export `grep 'config1=' /your/config/file`
export `grep 'config2=' /your/config/file`
But remember if you put this in a shell script file, then you'll need to eval the file rather than execute it to set the variables in the current shell instance.
You could do this:
variable=`sed -n 's/^config1=//p'`
Or if you are attempting to evaluate certain parts of your file, try something like
eval `grep ^config1= test.conf`
to have config1=Configuration 1 evaluated by the current shell. (With the example you provided, this will cause a syntax error, because the value cannot contain unquoted whitespace.)
I generally recommend beginners to stay away from backticks, but this is a situation where they are a good answer.

Resources