Pass two variables sequentially - linux

In Unix shell,git clone <url> will prompt user for username then password.
I defined $username and $password variables.
how could I pass two variables to the command in order.
I have tried
echo $password | echo $username | git clone <url>
,which did not work

There are several ways you can do this. What you probably should do, because it's more secure, is use a configuration where the script doesn't have to contain and pass the username and password. For example, you could set up ssh, or you could use a credential helper. (Details depend on your environment, so I'd recommend searching for existing questions and answers re: how to set those up.)
If you still want to have the script pass the values, you basically have two choices: You can use a form of the git command that takes the values on the command line (see brokenfoot's answer), or you can pass the values on STDIN (which is the approach you're attempting, but doesn't work quite the way you're attempting it).
When you use |, you're sending the "standard output" of the command on the left to the "standard input" of the command on the right. So when you chain commands like you show, the first echo is sending output to the second echo - which ignores it. That's not what you want.
You would need a single command that outputs the username, and end-of-line character, the password, and another end-of-line character. That's not easy to do with echo (at least, not portably). You could do something like
git clone *url* <<EOF
$username
$password
EOF

Let me pretend the question is neither git-related no security-related
and my answer to the literal question "How to pass two variables to a
program" is:
( echo $username; echo $password ) | git clone 'url'
That is, just output two strings separated by a newline (echo adds the newline); or do it in one
call to echo:
echo "$username
$password" | git clone 'url'

You can pass variable like so:
username="xyz"
password="123"
echo "git clone https://$username:$password#github.com/$username/repository.git"
Output:
git clone https://xyz:123#github.com/xyz/repository.git

Related

Extra quotes in a variable read from dialog in bash

I need to configure user.email for git in bash script, and my problem is that I don't know how to get this line to run: git config --global user.email "user#example.com"
right now my code run this this command without double quotes - user#example.com
and I tried to escape " " in every way that I found and almost every time the code run '"user#example.com"'
a fragment of my code in bash:
function get_email(){
e_mail=/tmp/tmp.sh.$$
dialog --clear \
--title "EMAIL" \
--inputbox "Enter your email" 8 40 2>"${e_mail}"
email=$(<"${e_mail}")
git config --global user.email "$email"
}
Can someone help me solve this problem?
dialog can under some circumstances (man page goes into detail) put literal literal double quotes in its output (which can be replaced with literal single-quotes using --single-quoted).
To strip them back out, use parameter expansion -- as shown in the below:
get_email() {
local email
email=$(dialog --stdout --clear \
--title "EMAIL" \
--inputbox "Enter your email" 8 40
)
git config --global user.email "${email//'"'/}"
}
Some notes:
"${email//'"'/}" expands $email, replacing all instances of " with an empty string. This is the solution to your immediate problem. The syntax involved is parameter expansion, documented at http://wiki.bash-hackers.org/syntax/pe
Don't use the function keyword. It makes your code incompatible with POSIX shells for absolutely no benefit.
Declare your locals with local (this isn't POSIX-defined, but even ash and dash support it) to prevent leaking into the surrounding scope.
Use --stdout on dialog to allow capture of output with command substitution.

Should I be using parameters or export environment variables?

I've always developed my shell scripts using parameters, on a daily-basis or even when developing some automation scripts. However, recently I've tried a different approach, exporting environment variables to my scripts.
#!/bin/bash
: ${USER?"Requires USER"}
: ${FIRST_NAME?"Requires FIRST_NAME"}
: ${LAST_NAME?"Requires LAST_NAME"}
: ${EMAIL?"Requires EMAIL"}
set -x
setup_git_account(){
su - "${USER}" -c "git config --global user.name '${FIRST_NAME} ${LAST_NAME}'"
su - "${USER}" -c "git config --global user.email '${EMAIL}'"
}
setup_git_account
This ensures a smaller code, easy checks if all the required variables are initialized and also, better understanding of what the script is doing, once all the variables are declared on outside.
export USER='john' && export FIRST_NAME='John' && export LAST_NAME='Doe' && export EMAIL='john.doe#email.com' && setup_git_account.sh
Which could be represented like this if implemented with receiving parameters:
setup_git_account.sh --user 'john' --firstname 'John' --lastname 'Doe' --email 'john.doe#email.com'
However, the last one, would need way more lines of code to implement the getopts switch case, check the passed parameters values, etc.
Anyway, I know we're used to the second approach, but I think the first approach also has several benefits. And I would like to hear more from you, if there's any downside between the presented approaches. And which one should I be using ?
Thanks!
A bit off-topic, the invocation syntax with environment variables for bash can be shorter, no need for export's:
USER='john' FIRST_NAME='John' LAST_NAME='Doe' EMAIL='john.doe#email.com' setup_git_account.sh
None of your values is optional; I would just use positional parameters.
: ${1?"Requires USER"}
: ${2?"Requires FIRST_NAME"}
: ${3?"Requires LAST_NAME"}
: ${4?"Requires EMAIL"}
sudo -u "$1" git config --global user.name "$2 $3" user.email "$4"
Providing the way for the user to specify values in an arbitrary order is just an unnecessary complication.
You would simply call the script with
setup_git_account.sh 'john' 'John' 'Doe' 'john.doe#email.com'
Reconsider whether the first and last names need to be separate arguments. They are combined into a single argument to git config by the script anyway; just take the name as a single argument as well.
setup_git_account.sh 'john' 'John Doe' 'john.doe#email.com'
(with the appropriate changes to the script as necessary).
I never use your approach. I think there are no drawbacks by using parameters. It's a common way to use parameters and if you are using longopts there are self-descriptive.
In my opinion env vars are a solution if you need data in different scripts.
Maybe you have problems to run such a script on systems where you don't be allowed to change the environment.
I've parameterized your variables using a guide I wrote a while back and even added --help.
This solution accepts environment variables as well as options (which will trump the variables):
while getopts e:f:hl:u:-: arg; do
case "$arg" in
e ) EMAIL="$OPTARG" ;;
f ) FIRST_NAME="$OPTARG" ;;
h ) do_help ;;
l ) LAST_NAME="$OPTARG" ;;
u ) USER_NAME="$OPTARG" ;;
- ) LONG_OPTARG="${OPTARG#*=}"
case $OPTARG in
email=?* ) EMAIL="$LONG_OPTARG" ;;
first*=?* ) FIRST_NAME="$LONG_OPTARG" ;;
help* ) do_help ;;
last*=?* ) LAST_NAME="$LONG_OPTARG" ;;
user=?* ) USER_NAME="$LONG_OPTARG" ;;
* ) echo "Illegal option/missing argument: --$OPTARG" >&2; exit 2 ;;
esac ;;
* ) exit 2 ;; # error messages for short options already given by getopts
esac
done
shift $((OPTIND-1))
HELP=" - see ${0##*/} --help"
: ${USER_NAME?"Requires USER_NAME$HELP"}
: ${FIRST_NAME?"Requires FIRST_NAME$HELP"}
: ${LAST_NAME?"Requires LAST_NAME$HELP"}
: ${EMAIL?"Requires EMAIL$HELP"}
su - "$USER_NAME" -c "git config --global user.name '$FIRST_NAME $LAST_NAME'"
su - "$USER_NAME" -c "git config --global user.email '$EMAIL'"
Note that I changed $USER to $USER_NAME to avoid conflicts with your local environment ($USER is your user name on your local Linux system!)
You can also extract the user's full name from the system:
FULL_NAME="$(getent passwd |awk -v u="$USER_NAME" -F: '$1 == u { print $5 }')"
(I see no reason to separate FIRST_NAME and LAST_NAME; what do you do for Jean Claude Van Damme? They're only used together anyway. Also note that not all users will have full names in the passwd file.)
This uses do_help to show the --help output. Here's an example of how that could look (I'd put this at the vary top of the script so somebody just reading it can get the synopsis; it's not in the above code block because I wanted to prevent the block from getting a scroll bar):
do_help() { cat <</help
Usage: ${0##*/} [OPTIONS]
-u USER_NAME, --user=USER_NAME
-f FIRST_NAME, --firstname=FIRST_NAME
-l LAST_NAME, --lastname=LAST_NAME
-e EMAIL, --email=EMAIL
Each option may also be passed through the environment as e.g. $EMAIL
Code taken from https://stackoverflow.com/a/41515444/519360
/help
}

Linux bash script - For loops issues

I'm working on a bash script that will add users in a batch process. This code goes as follows:
#!/bin/bash
# A script that creates users.
echo "This is a script to create new users on this system."
echo "How many users do you want to add?"
read am
echo " "
for i in {0..$am..1}
do
echo "Enter a username below:"
read usern
sudo useradd $usern
sudo passwd $usern
echo " "
echo "User $am '$usern' added."
done
In this case, I wanted to make 4 users. I went through and entered the username "callum3" and set the password as "1234" for ease of login. Once I input everything (correctly, may I add) the terminal window displays the following.
User 4 'callum3' added.
This shows that my for loop isn't actually working, when I can see nothing wrong with it. I have tried using a while loop with no luck there either.
Am I making a rookie mistake here or is there something deeper going on?
Although I suspected it, for a better understanding on what could be wrong with your script I pasted it in shellcheck.net. That the problem is in the line:
for i in {0..$am..1}
Bash doesn't support variables in brace range expansions. That is, you cannot use a variable in an expression like {..}.
Instead, use seq. With seq $var you get a sequence from 1 (default) to $var:
for i in $(seq "$am")
I feel like I'm missing something in that nobody has suggested an arithmetic for loop:
for ((i=0; i<am; i++)); do
…
done
This has the particular benefit in bash of being both readable and not requiring a subshell.
You can use:
for i in `seq 0 $((am-1))`
do
...
done
Sequence will start from 0 and end at $am-1

Passing variable to `expect` in bash array

I am trying to use a FOR loop to iterate over IP addresses (in a bash array), logs in, runs a script and then exits. The array is called ${INSTANCE_IPS[#]}. The following code doesn't work though, as expect doesn't seem to be able to accept the variable $instance.
for instance in ${INSTANCE_IPS[#]}
do
echo $instance
/usr/bin/expect -c '
spawn ssh root#$instance;
expect "?assword: ";
send "<password>\r";
expect "# ";
send ". /usr/local/bin/bootstrap.sh\r";
expect "# ";
send "exit\r" '
done
However, expect complains with:
can't read "instance": no such variable
while executing
"spawn ssh root#$instance"
There is another question on stackoverflow located here, that uses environmental variables to achieve this, however it doesn't allow me to iterate through different IP addresses like I can in an array.
Any help is appreciated.
Cheers
The problem is with quoting. Single quotes surrounding the whole block don't let Bash expand variables ($instance).
You need to switch to double quotes. But then, double quotes inside double quotes are not allowed (unless you escape them), so we are better off using single quotes with expect strings.
Try instead:
for instance in ${INSTANCE_IPS[#]}
do
echo $instance
/usr/bin/expect -c "
spawn ssh root#$instance;
expect '?assword: ';
send '<password>\r';
expect '# ';
send '. /usr/local/bin/bootstrap.sh\r';
expect '# ';
send 'exit\r' "
done
for instance in ${INSTANCE_IPS[&]} ; do
echo $instance
/usr/bin/expect -c '
spawn ssh root#'$instance' "/usr/local/bin/bootstrap.sh"
expect "password:"
send "<password>\r"
expect eof'
done
From the ssh man page:
If command is specified, it is executed on the remote host instead of a login shell.
Specifying a command means expect doesn't have to wait for # to execute your program, then wait for another # just to send the command exit. Instead, when you specify a command to ssh, it executes that command; it exits when done; and then ssh automatically closes the connection.
Alternately, put the value in the environment and expect can find it there
for instance in ${INSTANCE_IPS[&]} ; do
echo $instance
the_host=$instance /usr/bin/expect -c '
spawn ssh root#$env(the_host) ...
Old thread, and one of many, but I've been working on expect for several days. For anyone who comes across this, I belive I've found a doable solution to the problem of passing bash variables inside an expect -c script:
#!/usr/bin/env bash
password="TopSecret"
read -d '' exp << EOF
set user "John Doe"
puts "\$user"
puts "$password"
EOF
expect -c "$exp"
Please note that escaping quotations are typically a cited issue (as #Roberto Reale stated above), which I've solved using a heredoc EOF method, before passing the bash-variable-evaluated string to expect -c. In contrast to escaping quotes, all native expect variables will need to be escaped with \$ (I'm not here to solve all first-world problems--my afternoon schedule is slightly crammed), but this should greatly simplify the problem with little effort. Let me know if you find any issues with this proof of concept.
tl;tr: Been creating an [expect] daemon script with user authentication and just figured this out after I spent a whole day creating separated bash/expect scripts, encrypting my prompted password (via bash) with a different /dev/random salt each iteration, saving the encrypted password to a temp file and passing the salt to the expect script (highly discouraging anyone from easily discovering the password via ps, but not preventative since the expect script could be replaced). Now I should be able to effectively keep it in memory instead.

Accessing variable from ARGV

I'm writing a cPanel postwwwact script, if you're not familiar with the script its run after a new account is created. it relies on the user account variable being passed to the script which i then use for various things (creating databases etc). However, I can't seem to find the right way to access the variable i want. I'm not that good with shell scripts so i'd appreciate some advice. I had read somewhere that the value i wanted would be included in $ARGV{'user'} but this simply gives "root" as opposed to the value i need. I've tried looping through all the arguments (list of arguments here) like this:
#!/bin/sh
for var
do
touch /root/testvars/$var
done
and the value i want is in there, i'm just not sure how to accurately target it. There's info here on doing this with PHP or Perl but i have to do this as a shell script.
EDIT Ideally i would like to be able to call the variable by something other than $1 or $2 etc as this would create issues if an argument is added or removed
..for example in the PHP code here:
function argv2array ($argv) {
$opts = array();
$argv0 = array_shift($argv);
while(count($argv)) {
$key = array_shift($argv);
$value = array_shift($argv);
$opts[$key] = $value;
}
return $opts;
}
// allows you to do the following:
$opts = argv2array($argv);
echo $opts[‘user’];
Any ideas?
The parameters are passed to your script as a hash:
/scripts/$hookname user $user password $password
You can use associative arrays in Bash 4, or in earlier versions of Bash you can use built up variable names.
#!/bin/bash
# Bash >= 4
declare -A argv
for ((i=1;i<=${##};i+=2))
do
argv[${#:i:1}]="${#:$((i+1)):1}"
done
echo ${argv['user']}
Or
#!/bin/bash
# Bash < 4
for ((i=1;i<=${##};i+=2))
do
declare ARGV${#:i:1}="${#:$((i+1)):1}"
done
echo ${!ARGV*} # outputs all variable names that begin with ARGV
echo $ARGVuser
Running either:
$ ./argvtest user dennis password secret
dennis
Note: you can also use shift to step through the arguments, but it's destructive and the methods above leave $# ($1, $2, etc.) in place.
#!/bin/bash
# Bash < 4
# using shift (can use in Bash 4, also)
for ((i=1;i<=${##}+2;i++))
do
declare ARGV$1="$2"
# Bash 4: argv[$1}]="$2"
shift 2
done
echo ${!ARGV*}
echo $ARGVuser
If it's passed as a command-line parameter to the script, it's available as $1 if it's first parameter, $2 for the second, and so on.
Why not start off your script with something like
ARG_USER=$1
ARG_FOO=$2
ARG_BAR=$3
And then later in your script refer to $ARG_USER, $ARG_FOO and $ARG_BAR instead of $1, $2, and $3. That way, if you decide to change the order of arguments, or insert a new argument somewhere other than at the end, there is only one place in your code that you need to update the association between argument order and argument meaning.
You could even do more complex processing of $* to set your $ARG_WHATEVER variables, if it's not always going to be that all of the are specified in the same order every time.
You can do the following:
#!/bin/bash
for var in $argv; do
<do whatver you want with $var>
done
And then, invoke the script as:
$ /path/to/script param1 arg2 item3 item4 etc

Resources