Shell: save ssh command result to local variable - shell

I have a ssh command that sends back a number:
ssh ${user}#${hostname} "wc -l < ${workspace}/logs"
where ${user},${hostname},${workspace}are variables.
Now I want to save the result to a local variable called lines, I tried:
lines=${ssh ${user}#${hostname} "wc -l < ${workspace}/logs" }
But it does not work, why?

Your command should be wrapped in "()" not "{}" when assigning fetching the result to a variable. Also, the others are just variables not commands so don't need a wrapper (assuming they are defined in a script or something).
lines=$(ssh $user#$hostname "wc -l < $workspace/logs")

You should use $() to get the result of command, not ${} (in bash at least). The line below assumes that workspace is a variable defined on the host executing the lines=, not on the remote (if it was on the remote you would need to escape the $: \${workspace}/logs
lines=$(ssh ${user}#${hostname} "wc -l < ${workspace}/logs" )

Related

Executing the output as filename

In one of my Bash scripts, there's a point where I have a variable SCRIPT which contains the /path/to/an/exe, and what the script ultimately needs to do, is executing that executable. Therefore the last line of the script is
$($SCRIPT)
so that $SCRIPT is expanded to /path/to/an/exe, and $(/path/to/an/exe) executes the executable.
However, running shellcheck on the script generates this error:
In setscreens.sh line 7:
$($SCRIPT)
^--------^ SC2091: Remove surrounding $() to avoid executing output.
For more information:
https://www.shellcheck.net/wiki/SC2091 -- Remove surrounding $() to avoid e...
Is there a way I can rewrite that $($SCRIPT) in a more appropriate way? eval does not seem to be of much help here.
$($SCRIPT) indeed does not do what you think it does.
The outer $() will execute any commands inside the parenthesis and execute the result string.
The inner $SCRIPT will expand to the value of the SCRIPT variable and execute this string while splitting words on spaces/
If you want to execute the command contained into the SCRIPT variable, you just write as an example:
SCRIPT='/bin/ls'
"$SCRIPT" # Will execute /bin/ls
Now if you also need to handle arguments with your SCRIPT variable command call:
SCRIPT='/bin/ls'
"$SCRIPT" -l # Will execute /bin/ls -l
To also store or build arguments dynamically, you'd need an array instead of a string variable.
Example:
SCRIPT=(/bin/ls -l)
"${SCRIPT[#]}" # Will execute /bin/ls -l
SCRIPT+=(/etc) # Add /etc to the array
"${SCRIPT[#]}" # Will execute /bin/ls -l /etc
It worked for me with sh -c:
$ chrome="/opt/google/chrome/chrome"
$ sh -c "$chrome"
Opening in existing browser session.
It also passed the ShellCheck without any issues.
with bash, just use $SCRIPT:
cat <<'EOF' > test.sh
SCRIPT='echo aze rty'
$SCRIPT
EOF
bash test.sh
produce:
aze rty

Assigning a variable in a shell script for use outside of the script

I have a shell script that sets a variable. I can access it inside the script, but I can't outside of it. Is it possible to make the variable global?
Accessing the variable before it's created returns nothing, as expected:
$ echo $mac
$
Creating the script to create the variable:
#!/bin/bash
mac=$(cat \/sys\/class\/net\/eth0\/address)
echo $mac
exit 0
Running the script gives the current mac address, as expected:
$ ./mac.sh
12:34:56:ab:cd:ef
$
Accessing the variable after its created returns nothing, NOT expected:
$ echo $mac
$
Is there a way I can access this variable at the command line and in other scripts?
A child process can't affect the parent process like that.
You have to use the . (dot) command — or, if you like C shell notations, the source command — to read the script (hence . script or source script):
. ./mac.sh
source ./mac.sh
Or you generate the assignment on standard output and use eval $(script) to set the variable:
$ cat mac.sh
#!/bin/bash
echo mac=$(cat /sys/class/net/eth0/address)
$ bash mac.sh
mac=12:34:56:ab:cd:ef
$ eval $(bash mac.sh)
$ echo $mac
12:34:56:ab:cd:ef
$
Note that if you use no slashes in specifying the script for the dot or source command, then the shell searches for the script in the directories listed in $PATH. The script does not have to be executable; readable is sufficient (and being read-only is beneficial in that you can't run the script accidentally).
It's not clear what all the backslashes in the pathname were supposed to do other than confuse; they're unnecessary.
See ssh-agent for precedent in generating a script like that.

Why doesn't LIMIT=\`ulimit -u\` work in bash?

In my program I need to know the maximum number of process I can run. So I write a script. It works when I run it in shell but but when in program using system("./limit.sh"). I work in bash.
Here is my code:
#/bin/bash
LIMIT=\`ulimit -u\`
ACTIVE=\`ps -u | wc -l \`
echo $LIMIT > limit.txt
echo $ACTIVE >> limit.txt
Anyone can help?
Why The Original Fails
Command substitution syntax doesn't work if escaped. When you run:
LIMIT=\`ulimit -u\`
...what you're doing is running a command named
-u`
...with the environment variable named LIMIT containing the value
`ulimit
...and unless you actually have a command that starts with -u and contains a backtick in its name, this can be expected to fail.
This is because using backticks makes characters which would otherwise be syntax into literals, and running a command with one or more var=value pairs preceding it treats those pairs as variables to export in the environment for the duration of that single command.
Doing It Better
#!/bin/bash
limit=$(ulimit -u)
active=$(ps -u | wc -l)
printf '%s\n' "$limit" "$active" >limit.txt
Leave off the backticks.
Use modern $() command substitution syntax.
Avoid multiple redirections.
Avoid all-caps names for your own variables (these names are used for variables with meaning to the OS or system; lowercase names are reserved for application use).
Doing It Right
#!/bin/bash
exec >limit.txt # open limit.txt as output for the rest of the script
ulimit -u # run ulimit -u, inheriting that FD for output
ps -u | wc -l # run your pipeline, likewise with output to the existing FD
You have a typo on the very first line: #/bin/bash should be #!/bin/bash - this is often known as a "shebang" line, for "hash" (#) + "bang" (!)
Without that syntax written correctly, the script is run through the system's default shell, which will see that line as just a comment.
As pointed out in comments, that also means only the standardised options available to the builtin ulimit command, which doesn't include -u.

update a variable in background in bashrc

I would like the variable NUSERS='who | wc -l' to be updated every 2 seconds in order to display the number of connected users in the prompt with PS1='\u#\h-${NUSERS}:\w $' defined in the .bashrc file.
I tryed: watch NUSERS='who | wc -l' &>/dev/null & in the .bashrc... it didn't work
I tryed: while true; do NUSERS='who | wc -l' && sleep 2; done & in the .bashrc ... it didn't work neither
I don't understand why this doesn't work. I would like to avoid screen and nohup because I don't want the command to run when I exit the ssh session.
The parent shell doesn't see the variable updates in its children.
Since your purpose is to have an update not every two seconds, but each time a new prompt is displayed, you may use the PROMPT_COMMAND variable for this.
As per the reference manual, about the PROMPT_COMMAND variable:
If set, the value is interpreted as a command to execute before the printing of each primary prompt ($PS1).
Exactly what you need!
Put this in your .bashrc file:
PROMPT_COMMAND='NUSERS=$(wc -l < <(who))'
PS1='\u#\h-$NUSERS:\w $'
and you'll be good.
actually, it is possible to insert a command directly in the PS1 variable declaration in the .bashrc file
PS1='\u#\h-`who | wc -l`:\w $'

Bash export variables received lines by lines

I got a simple problem but very boring.
The goal is to write a shell script that run on EC2 instance to exports tags for rest of the script... Something like:
ec2-describe-tags [...]
| while IFS=':' read name value; do
export "$name"="$value"
done
Not so uggly but don't work, of course cause the export is in the while loop, executed in pipe.
My question is: how to write this correctly? Of course I cannot predict names nor numbers of received tags.
Try this:
while IFS=: read name value; do
export $name="$value"
done < <(ec2-describe-instance ...)
A pipeline runs the commands in a subshell, so the variables don't persist when it's done.
Since it seems that the output consists solely of lines of the form name:value, you should just be able to do
while read; do
export "$REPLY" # Using default variable set by read
done < <( ec2-describe-tags ... | sed 's/:/=' )
You could even get fancy with the readarray command (if available) and simply run
readarray -t env_vars < <(ec2-describe-tags ... | sed 's/:/=')
export "${env_vars[#]}"
The process substitution allows the while loop to run in the current shell, so the exported variables will be put in the shell's environment.
If you are using bash 4.2 or later, you can set the lastpipe option, which allows the last command in a pipe to run in the current shell instead of a subshell, allowing you to keep your current pipeline.

Resources