I have a few servers I need to configure and all the servers have the same user, I need to have the user to key in the username and new password so that I can run this script to configure all servers. This is the error I am currently facing, and sorry I am new to Linux scripting please help me out, Thank you!
$ cat serverlist.txt
192.168.1.8
192.168.1.7
192.168.1.6$ cat changepasswd.sh
#!/bin/bash
read -p "Enter username: " username
read -p "Enter new password: " new_password
for server in 'cat serverlist.txt'
do echo -e "Server IP is: $server"
ssh $server 'echo "new_password\nnew_password" | passwd username
done$ chmod 777 changepasswd.sh
$ sh changepasswd.sh
Enter username: vncuser
Enter new password: abc12345
changepasswd.sh: 9: changepasswd.sh: Syntax error: Unterminated quoted string 2 1 Answer
Like the error says, there is an unterminated quoted string. The unpaired single-quote (') is between $server and echo. If you only fix it like this
ssh $server 'echo "new_password\nnew_password" | passwd username'you will encounter more problems.
Problems I noticed (starting from general ones, not necessarily most severe):
You used a shebang (
#!/bin/bash) but then called the script bysh changepasswd.sh. In this case the shebang is totally ignored and the interpreter issh. Usually it's better to rely on the shebang and run a script as./the_script; this way the caller does not need to wonder which interpreter to use, the right interpreter is in the shebang.You made the script executable (with
chmod) butsh changepasswd.shdoes not need it to be executable. On the other hand calling with./changepasswd.shwould need it to be executable.You used
shas the interpreter, butread -pis not portable. Yourshmay support it, other implementations may not. Usingbashwill allow you not only to use-preliably, but also-s(for not echoing the password). One way or another you should use-randIFS=''(empty string) especially when reading a password. In Bash seehelp readto learn what these options do.If you decide to use the shebang, it's better to get rid of the
.shsuffix. If you ever port the script to another interpreter (or even compile a binary executable), the namechangepasswdwill still fit, whilechangepasswd.shwould need to be changed or it would be confusing.chmod 777makes the script writable for anyone. If the script is in a directory where other users have certain rights, they will be able to change the script. E.g. they can inject a line that will send/store$new_passwordfor them.744may be the right mode.It's advisable to ask for a new password twice and abort if the two strings don't match.
for server in 'cat serverlist.txt'will assigncat serverlist.txt(string) to the variable. You meant`cat serverlist.txt`or$(cat serverlist.txt). The latter is better.In general you should double-quote
$( )to prevent word splitting and filename generation; but in this case you do want word splitting (yet you don't want filename generation). Looping over the lines of a file (serverlist.txtin your case) can be done withread(but there are pitfalls). However if your file contains IP addresses or simple hostnames only thenfor server in $(cat serverlist.txt)seems fine. Just remember it's not a good general way.Still you should double-quote variables like
$server."new_password\nnew_password"is just a string. You want to expand variables:"$new_password\n$new_password". In general, when concatenating, you may want to use${new_password}.The "fixed" snippet
ssh $server 'echo "$new_password\n$new_password" | passwd $username'will not expand
$new_passwordnor$usernamelocally, because these are single-quoted. The remote shell will expand them (most likely to empty strings). To expand locally, the variables should be double-quoted:ssh "$server" "echo '$new_password\n$new_password' | passwd '$username'"But this is still error prone, keep reading.
The SSH server will get the entire command string as a string. A remote shell will interpret it. This means a single-quote character in (locally expanded)
$new_password(or$username) will break the code. This way you can even inject code. It's not about possible code injection (I understand you can execute any code anyway); it's about setting an unexpected password. There are few ways to deal with this:- In general you can tell
sshto pass some variables to the remote side, if the server allows it. Then a remote shell could expand them safely. This depends on the server configuration; plus on the client side it's hard (and not elegant) to define the variables forsshdynamically. You shouldn't do this in this case, but if you want to learn more then investigatePermitUserEnvironmentinman 5 sshd_config. - Bash allows you to expand a variable in a special way, so after the result is parsed in a shell, it becomes the original string again. This is done with
${variable@Q}. The expanded string is properly quoted and/or escaped. - Since you're reading from a pipe on the remote side, you can pipe to
sshlocally and avoid passing (locally expanded)$new_passwordas a string that is about to be parsed.
Whatever you choose, remember you need to properly quote for the local shell and separately for the remote shell (in case of
${variable@Q}the localQoperator handles this part).- In general you can tell
Do not use
echoto pass "random" strings, preferprintf. If you decide to pipe a variable tosshin Bash, then a here string may be a good way. In your case you need to pass$new_passwordtwice (plus newlines), so I would go withprintfanyway.
The script may look like this:
#!/bin/bash
IFS= read -rp "Enter username: " username
IFS= read -rsp "Enter new password: " new_password
echo # just to get a newline
IFS= read -rsp "Repeat new password: " new_password_2
echo
[ "$new_password" = "$new_password_2" ] || { echo "Mismatch. Aborting."; exit 1; }
for server in $(cat serverlist.txt) do echo "Server IP is: $server" printf '%s\n' "$new_password" "$new_password" | ssh "$server" "passwd -- ${username@Q}"
doneYou need to make it executable (chmod u+x changepasswd) and run it with ./changepasswd.
Notes:
- The script assumes no
passwdwill ask for the current password. This assumption originates in the script you published. - The script assumes all remote
passwdsupport double dash. ssh "$server" "passwd -- ${username@Q}"could bessh "$server" "passwd '$username'", if you're sure expanded$usernameis safe. Basic validation can be done locally.