How to pass shell variables in "echo" command - shell

I have she script as the below content
chr=$0
start=$1
end=$2
echo -e "$chr\t$start\t$end" > covdb_input.bed
How do i pass the chr,Start and end variables in to echo command.. or write same to file "covdb_input.bed" with TAB sep as in echo command.

You're doing everything right, except that you probably initialize your variables with the wrong things.
I'm assuming you get arguments for the script (or shell function), and that you want to use these. Then pick the positional variables from $1 and onwards as $0 will usually contain the name of the current shell script or shell function.
Also, you might find people scoffing about the use of -e with echo (it's a common but non-standard option). Instead of using echo you could use printf like this:
printf "%s\t%s\t%s" "$chr" "$start" "$end" >myfile.bed
Or just
printf "$chr\t$start\t$end" >myfile.bed

Related

How can I save environment variables in a file using BASH? [duplicate]

I have two shell scripts that I'd like to invoke from a C program. I would like shell variables set in the first script to be visible in the second. Here's what it would look like:
a.sh:
var=blah
<save vars>
b.sh:
<restore vars>
echo $var
The best I've come up with so far is a variant on "set > /tmp/vars" to save the variables and "eval $(cat /tmp/vars)" to restore them. The "eval" chokes when it tries to restore a read-only variable, so I need to grep those out. A list of these variables is available via "declare -r". But there are some vars which don't show up in this list, yet still can't be set in eval, e.g. BASH_ARGC. So I need to grep those out, too.
At this point, my solution feels very brittle and error-prone, and I'm not sure how portable it is. Is there a better way to do this?
One way to avoid setting problematic variables is by storing only those which have changed during the execution of each script. For example,
a.sh:
set > /tmp/pre
foo=bar
set > /tmp/post
grep -v -F -f/tmp/pre /tmp/post > /tmp/vars
b.sh:
eval $(cat /tmp/vars)
echo $foo
/tmp/vars contains this:
PIPESTATUS=([0]="0")
_=
foo=bar
Evidently evaling the first two lines has no adverse effect.
If you can use a common prefix on your variable names, here is one way to do it:
# save the variables
yourprefix_width=1200
yourprefix_height=2150
yourprefix_length=1975
yourprefix_material=gravel
yourprefix_customer_array=("Acme Plumbing" "123 Main" "Anytown")
declare -p $(echo ${!yourprefix#}) > varfile
# load the variables
while read -r line
do
if [[ $line == declare\ * ]]
then
eval "$line"
fi
done < varfile
Of course, your prefix will be shorter. You could do further validation upon loading the variables to make sure that the variable names conform to your naming scheme.
The advantage of using declare is that it is more secure than just using eval by itself.
If you need to, you can filter out variables that are marked as readonly or select variables that are marked for export.
Other commands of interest (some may vary by Bash version):
export - without arguments, lists all exported variables using a declare format
declare -px - same as the previous command
declare -pr - lists readonly variables
If it's possible for a.sh to call b.sh, it will carry over if they're exported. Or having a parent set all the values necessary and then call both. That's the most secure and sure method I can think of.
Not sure if it's accepted dogma, but:
bash -c 'export foo=bar; env > xxxx'
env `cat xxxx` otherscript.sh
The otherscript will have the env printed to xxxx ...
Update:
Also note:
man execle
On how to set environment variables for another system call from within C, if you need to do that. And:
man getenv
and http://www.crasseux.com/books/ctutorial/Environment-variables.html
An alternative to saving and restoring shell state would be to make the C program and the shell program work in parallel: the C program starts the shell program, which runs a.sh, then notifies the C program (perhaps passing some information it's learned from executing a.sh), and when the C program is ready for more it tells the shell program to run b.sh. The shell program would look like this:
. a.sh
echo "information gleaned from a"
arguments_for_b=$(read -r)
. b.sh
And the general structure of the C program would be:
set up two pairs of pipes, one for C->shell and one for shell->C
fork, exec the shell wrapper
read information gleaned from a on the shell->C pipe
more processing
write arguments for b on the C->shell pipe
wait for child process to end
I went looking for something similar and couldn't find it either, so I made the two scripts below. To start, just say shellstate, then probably at least set -i and set -o emacs which this reset_shellstate doesn't do for you. I don't know a way to ask bash which variables it thinks are special.
~/bin/reset_shellstate:
#!/bin/bash
__="$PWD/shellstate_${1#_}"
trap '
declare -p >"'"$__"'"
trap >>"'"$__"'"
echo cd \""$PWD"\" >>"'"$__"'" # setting PWD did this already, but...
echo set +abefhikmnptuvxBCEHPT >>"'"$__"'"
echo set -$- >>"'"$__"'" # must be last before sed, see $s/s//2 below
sed -ri '\''
$s/s//2
s,^trap --,trap,
/^declare -[^ ]*r/d
/^declare -[^ ]* [A-Za-z0-9_]*[^A-Za-z0-9_=]/d
/^declare -[^ ]* [^= ]*_SESSION_/d
/^declare -[^ ]* BASH[=_]/d
/^declare -[^ ]* (DISPLAY|GROUPS|SHLVL|XAUTHORITY)=/d
/^declare -[^ ]* WINDOW(ID|PATH)=/d
'\'' "'"$__"'"
shopt -op >>"'"$__"'"
shopt -p >>"'"$__"'"
declare -f >>"'"$__"'"
echo "Shell state saved in '"$__"'"
' 0
unset __
~/bin/shellstate:
#!/bin/bash
shellstate=shellstate_${1#_}
test -s $shellstate || reset_shellstate $1
shift
bash --noprofile --init-file shellstate_${1#_} -is "$#"
exit $?

How do I pass in an un-evaluated variable to a shell script, and have the script evaluate it after sourcing another script?

I have a script that is sourcing a second script and need to do the following:
I want to pass in a variable name to the first script like this: sh firstScript.sh variable=$variableName
The first script will then source the second script, which contains the value of variableName
I'm then going to print the evaluated variable
I know that I can do something like \$variableName to pass in the variable name, but I can't figure out how to get the first script to then evaluate the variable using the exported variables from the second script. What am I missing here?
Here's what I wound up doing:
I'm passing in an entire string, with the variable embedded in the middle of the string like so:
sh firstScript.sh --message="This is a message \${variableName}"
In the first script, I'm doing these steps:
Extract the entire string in to an array
Pull out the embedded variables
Evaluate the embedded variables against the sourced script
Do a string replace to put the value in the original string
When I was done, it looked like this:
IFS=';' args=(${#//--/;}); unset IFS
source secondScript.sh
case ${arg^^} in
MESSAGE=*)
message="${arg#*=}"
messageVars=$(echo ${message} | grep -o "\${\w*}")
for messageVar in ${messageVars[*]}; do
messageVar=${messageVar#*\{}
messageVar=${messageVar%\}*}
messageVarVal=${!messageVar}
echo "messageVar: ${messageVar}"
echo "messageVarVal: ${messageVarVal}"
message=${message//"\${${messageVar}}"/"${messageVarVal}"}
done
echo "Message: ${message}"
;;
esac
I hope this is what you want:
firstScript.sh
varname="$1"
if [[ -z $varname ]]; then
echo "usage: $0 varname"
exit
fi
source ./secondScript.sh
declare -n ref="$varname"
echo "varname=$varname value=$ref"
secondScript.sh
foo=2
Then execute the script with:
./firstScript.sh foo
Output:
varname=foo value=2
The -n option to the declare creates a reference to another variable.
The bash version should be 4.3 or later to enjoy the functionality.
[Alternative]
You can also write the firstScript.sh as:
firstScript.sh
varname="$1"
if [[ -z $varname ]]; then
echo "usage: $0 varname"
exit
fi
source ./secondScript.sh
echo "varname=$varname value=${!varname}"
which will produce the same result.
The ${!varname} notation is called indirect expansion in which varname is expanded as a name of a variable.
It has been introduced since bash2.
Hope this helps.

Portably setting environment variables from a shell script

I have this shell script that I would like to modify to ensure it runs correctly in a Bash environment.
Here's the script:
#!/bin/zsh
# Some constants. The first two will become env variables.
UPDATE_DNS_API_HOST="https://example.com"
UPDATE_DNS_API_URL="$UPDATE_DNS_API_HOST/my_end_point"
CURRENT_PUBLIC_IP=$(curl -s "$UPDATE_DNS_API_URL" | grep -o '".*"' | tr -d '"')
if [[ $PUBLIC_IP == $CURRENT_PUBLIC_IP ]]; then
echo "Current IP: "$CURRENT_PUBLIC_IP" already set."
else
response=$(curl -s -H "Content-Type: application/json" \
--data "$CURRENT_PUBLIC_IP" "$UPDATE_DNS_API_URL")
echo $response
export PUBLIC_IP="$CURRENT_PUBLIC_IP"
fi
Here are my questions:
Should I change the first line to #!/bin/bash
It's unclear when variables need quotes and when they don't, especially in conditional statements. Can you point me to some resources here?
I've seen variations in conditionals regarding single bracket vs double bracket. Which one should I be using?
After running the script, $PUBLIC_IP does not appear to be set. Is there a different way I should be setting the env variable?
Any other feedback is welcome.
One pertinent thing to keep in mind here is that UNIX processes can modify the environment variable for themselves and future children they start -- not their parents, without that parent process directly participating.
If your intent is to set a variable in the enclosing shell, one fairly common way to do this is to emit shell commands on stdout. This means that anything that isn't a shell command should be moved to stderr (which is appropriate practice anyhow, since stderr is specified as appropriate for informational text and status content).
This version does require bash, as opposed to /bin/sh, but uses printf '%q' to ensure that it's able to generate variable names in an eval-safe manner that all ksh derivatives (ksh, bash, zsh) should be able to read.
#!/bin/bash
# note that while this runs with bash, ksh and zsh will also be able to eval its output
# ...POSIX sh too, when there aren't nonprintable characters causing $''-style quoting
# ...to be used.
# usage: emit_cmd varname ...
#
# emit code that defines a variable when evaluated on stdout
emit_cmd() {
for varname; do
printf 'export %q=%q; ' "$varname" "${!varname}"
done
}
# Some constants. The first two will become env variables.
UPDATE_DNS_API_HOST="https://example.com"
UPDATE_DNS_API_URL="$UPDATE_DNS_API_HOST/my_end_point"
# print definitions of those variables to stdout
emit_cmd UPDATE_DNS_API_HOST UPDATE_DNS_API_URL
CURRENT_PUBLIC_IP=$(curl -s "$UPDATE_DNS_API_URL" | grep -o '".*"' | tr -d '"')
if [[ $PUBLIC_IP = $CURRENT_PUBLIC_IP ]]; then
echo "Current IP: $CURRENT_PUBLIC_IP already set." >&2
else
response=$(curl -s -H "Content-Type: application/json" \
--data "$CURRENT_PUBLIC_IP" "$UPDATE_DNS_API_URL")
echo "$response" >&2
PUBLIC_IP="$CURRENT_PUBLIC_IP" emit_cmd PUBLIC_IP
fi
If this script is saved under the name ip-lookup, the variables it defines can be imported into the current shell with:
eval "$(ip-lookup)"
Using this convention keeps compatibility with existing UNIX tools such as ssh-agent which need to modify environment variables.
Note that I'm keeping the existing conventions with respect to variable names, but if you have the opportunity, you should switch to lower-case names to comply with relevant POSIX convention.
If you want someone who invokes script.sh to run it under bash instead of zsh, then you must fix the shebang.
The inner pair of double quotes in:
echo "Current IP: "$CURRENT_PUBLIC_IP" already set."
are unorthodox and not a good idea. Use:
echo "Current IP: $CURRENT_PUBLIC_IP already set."
You probably want double quotes around:
echo "$response"
Otherwise, I think you're OK. As to resources, you can look at the Bash manual, or use facilities like shellcheck.com, or consult the Bash FAQ.
If you use [[ … ]] you'll probably be OK as written. The normal rules of shell syntax are suspended in [[ … ]] and confuse the hell out of people who learned Bourne shell and can't be bothered to learn that part of Bash.
You'd have to dot (. script.sh) or source (source script.sh) the script for the export PUBLIC_IP="$CURRENT_PUBLIC_IP" to have any effect on the calling shell. Otherwise, it sets the environment of the shell that runs the script, but doesn't affect the calling shell.
If you do decide you want to use the script with the dot command, then you should consider how many of the variables it sets should be unset before it finishes. And maybe whether any of them are set before it starts. Creating a function with all variables declared as local except the one you want to export makes life easier. You don't have to worry about this when the script is run as a separate process.

Shell scripting: cat vs echo for output

I've written a small script in bash that parses either the provided files or stdin if no file is given to produce some output. What is the best way to redirect the parsed output to stdout (at the end of the script the result is stored in a variable). Should I use cat or echo, or is there another preferred method?
Use the printf command:
printf '%s\n' "$var"
echo is ok for simple cases, but it can behave oddly for certain arguments. For example, echo has a -n option that tells it not to print a newline. If $var happens to be -n, then
echo "$var"
won't print anything. And there are a number of different versions of echo (either built into various shells or as /bin/echo) with subtly different behaviors.
echo. You have your parsed data in a variable, so just echo "$var" should be fine. cat is used to print the contents of files, which isn't what you want here.
echo is a fine way to do it. You will have to jump through a few hoops if you want cat to work.

Specify command line arguments like name=value pairs for shell script

Is it possible to pass command line arguments to shell script as name value pairs, something like
myscript action=build module=core
and then in my script, get the variable like
$action and process it?
I know that $1....and so on can be used to get variables, but then won't be name value like pairs. Even if they are, then the developer using the script will have to take care of declaring variables in the same order. I do not want that.
This worked for me:
for ARGUMENT in "$#"
do
KEY=$(echo $ARGUMENT | cut -f1 -d=)
KEY_LENGTH=${#KEY}
VALUE="${ARGUMENT:$KEY_LENGTH+1}"
export "$KEY"="$VALUE"
done
# from this line, you could use your variables as you need
cd $FOLDER
mkdir $REPOSITORY_NAME
Usage
bash my_scripts.sh FOLDER="/tmp/foo" REPOSITORY_NAME="stackexchange"
STEPS and REPOSITORY_NAME are ready to use in the script.
It does not matter what order the arguments are in.
Changelog
v1.0.0
In the Bourne shell, there is a seldom-used option '-k' which automatically places any values specified as name=value on the command line into the environment. Of course, the Bourne/Korn/POSIX shell family (including bash) also do that for name=value items before the command name:
name1=value1 name2=value2 command name3=value3 -x name4=value4 abc
Under normal POSIX-shell behaviour, the command is invoked with name1 and name2 in the environment, and with four arguments. Under the Bourne (and Korn and bash, but not POSIX) shell -k option, it is invoked with name1, name2, name3, and name4 in the environment and just two arguments. The bash manual page (as in man bash) doesn't mention the equivalent of -k but it works like the Bourne and Korn shells do.
I don't think I've ever used it (the -k option) seriously.
There is no way to tell from within the script (command) that the environment variables were specified solely for this command; they are simply environment variables in the environment of that script.
This is the closest approach I know of to what you are asking for. I do not think anything equivalent exists for the C shell family. I don't know of any other argument parser that sets variables from name=value pairs on the command line.
With some fairly major caveats (it is relatively easy to do for simple values, but hard to deal with values containing shell meta-characters), you can do:
case $1 in
(*=*) eval $1;;
esac
This is not the C shell family. The eval effectively does the shell assignment.
arg=name1=value1
echo $name1
eval $arg
echo $name1
env action=build module=core myscript
You said you're using tcsh. For Bourne-based shells, you can drop the "env", though it's harmless to leave it there. Note that this applies to the shell from which you run the command, not to the shell used to implement myscript.
If you specifically want the name=value pairs to follow the command name, you'll need to do some work inside myscript.
It's quite an old question, but still valid
I have not found the cookie cut solution. I combined the above answers. For my needs I created this solution; this works even with white space in the argument's value.
Save this as argparse.sh
#!/bin/bash
: ${1?
'Usage:
$0 --<key1>="<val1a> <val1b>" [ --<key2>="<val2a> <val2b>" | --<key3>="<val3>" ]'
}
declare -A args
while [[ "$#" > "0" ]]; do
case "$1" in
(*=*)
_key="${1%%=*}" && _key="${_key/--/}" && _val="${1#*=}"
args[${_key}]="${_val}"
(>&2 echo -e "key:val => ${_key}:${_val}")
;;
esac
shift
done
(>&2 echo -e "Total args: ${#args[#]}; Options: ${args[#]}")
## This additional can check for specific key
[[ -n "${args['path']+1}" ]] && (>&2 echo -e "key: 'path' exists") || (>&2 echo -e "key: 'path' does NOT exists");
#Example: Note, arguments to the script can have optional prefix --
./argparse.sh --x="blah"
./argparse.sh --x="blah" --yy="qwert bye"
./argparse.sh x="blah" yy="qwert bye"
Some interesting use cases for this script:
./argparse.sh --path="$(ls -1)"
./argparse.sh --path="$(ls -d -1 "$PWD"/**)"
Above script created as gist, Refer: argparse.sh
Extending on Jonathan's answer, this worked nicely for me:
#!/bin/bash
if [ "$#" -eq "0" ]; then
echo "Error! Usage: Remind me how this works again ..."
exit 1
fi
while [[ "$#" > "0" ]]
do
case $1 in
(*=*) eval $1;;
esac
shift
done

Resources