How to make $! to work inside `sh -c "$!"` [duplicate] - bash

This question already has answers here:
How to save multiple $! into variables and use it later in bash?
(3 answers)
Closed 5 years ago.
Please help to see if there is someway to make below scrip workable?
name='bob'
# below script reports 'bash: !: event not found' error
sh -c "echo $name; sleep 20 & pid1=$!; sleep 10 & pid2=$!; echo \"pid1: $pid1, pid2: $pid2\""
# below script $name will not become bob
sh -c 'echo $name; sleep 20 & pid1=$!; sleep 10 & pid2=$!; echo \"pid1: $pid1, pid2: $pid2\"'
[Add 01/03]
This question is somewhat duplicate of this one, it's my fault that the description of the original question is not clear and accurate enough, and I try to create a new one to make it more specific and clear.

You seem to be trying to solve the event not found problem.
It happens when you have set -H in Bash and use an exclamation mark in an interactive command (outside of single quotes or a backslash escape).
I have posted an answer and I think half a dozen comments explaining what is happening and how to fix it.
set +H
This command disables the Bash history mechanism which uses the exclamation mark in interactive sessions.
In addition, within double quotes, you have to escape the dollar sign to pass it verbatim to the subshell.
sh -c "echo \"$name\"
sleep 20 & pid1=\$!
sleep 10 & pid2=\$!
echo \"pid1: \$pid1, pid2: \$pid2\""

The comments propose mixing quotes and disabling history expansion, but a better solution (IMO) is to pass the name as a parameter:
sh -c 'echo "$1"; sleep 20 & pid1=$!; ...' sh "$name"

Related

How to save multiple $! into variables and use it later in bash?

I want to get the pids of two background processes,
sleep 20 & pid1=$\!; sleep 10 & pid2=$\!; echo "pid1: $pid1, pid2: $pid2"
and get output like below:
[1] 124646
[2] 124648
pid1: $!, pid2: $!
the output I desired to get is like:
pid1: 124646, pid2: 124648
Anyone know why and can help to achieve this?
[add 2018/01/02]
Sorry for really late response, one hand is busy, another is that I want to verify the script.
The actual script I want to run is like below:
sh -c "sleep 20 & pid1=$!; sleep 10 & pid2=$!; echo \"pid1: $pid1, pid2: $pid2\""
and as it will report bash: !: event not found, so I tried to add \ and become:
sh -c "sleep 20 & pid1=$\!; sleep 10 & pid2=$\!; echo \"pid1: $pid1, pid2: $pid2\""
and for make the problem simple, I just rmeove sh -c while this make it a quite different problem.
for my problem, I found out that below script will work:
sh -c 'sleep 20 & pid1=$!; sleep 10 & pid2=$!; echo "pid1: $pid1, pid2: $pid2"'
Yet there is another question, how to make below script to work:
name='bob'
# below script reports 'bash: !: event not found' error
sh -c "echo $name; sleep 20 & pid1=$!; sleep 10 & pid2=$!; echo \"pid1: $pid1, pid2: $pid2\""
# below script $name will not become bob
sh -c 'echo $name; sleep 20 & pid1=$!; sleep 10 & pid2=$!; echo \"pid1: $pid1, pid2: $pid2\"'
The backslash is causing the value to be the string $! verbatim. Don't put a backslash in the assignment.
On the command line, you may want to temporarily set +H to avoid getting event not found warnings; but this only affects the interactive shell. In a script, set -H is never active (and would be meaningless anyway).
(I'm speculating this is the reason you put the backslash there in the first place. If not, simply just take it out.)
Your syntax kindly incorrect, try this:
[root#XXX ~]# sleep 5 & pid1=$!; sleep 6 & pid2=$!; echo "pid1: ${pid1}, pid2: ${pid2}"
[1] 2308
[2] 2309
pid1: 2308, pid2: 2309
I have accomplished this in the past by using bash arrays to hold the PIDs. I had a sequence of database imports to run and when handled sequentially they took ~8 hours to complete. I launched them all as background processes and tracked the list of PIDs to watch for completion and it got the processing time down to 45 minutes.
Here is an example of launching background processes, storing the PIDs in an array, and then printing all of the array values:
$ pids=()
$ sleep 20 &
22991
$ pids+=($!)
$ sleep 20 &
23298
$ pids+=($!)
$ j=0;for i in "${pids[#]}";do ((j=j+1));echo 'pid'$j': '$i;done
pid1: 22991
pid2: 23298

Bash script : how to interpret variables in sub-bash command? [duplicate]

This question already has answers here:
Difference between single and double quotes in Bash
(7 answers)
Closed 6 years ago.
I try to have a script to add ips to /etc/hosts, but if it does add a line to /etc/hosts, the line is empty.
I guess there is an issue with the variable name exchanged by value into the ["] :
machines=("dell" "pb")
ips=( "192.168.0.70" "192.168.0.60")
n=-1
for nom_machine in "${machines[#]}"
do
n=$(( $n + 1 ))
ip_machine=${ips[$n]}
link=" $ip_machine $nom_machine"
$(sudo /bin/bash -c 'echo -e $link >> /etc/hosts')
done
Any idea why this add empty lines to /etc/hosts ?
Variables aren't expanded in single quotes, only double quotes. You also don't need the $(...) around sudo.
sudo bash -c "echo -e $link >> /etc/hosts"
As a script style issue, I would suggest removing the sudo call altogether. Instead, expect the person running the script run it with sudo if they don't have sufficient permissions. Your script would just have:
echo -e "$link" >> /etc/hosts

BASH - getting UID on shell script does not work [duplicate]

This question already has an answer here:
Blank first line of shell script: explain behavior of UID variable
(1 answer)
Closed 6 years ago.
Hi I have a question about bash.
and I'm new to it.
I made a file named "test.sh" and its contents is
#!/bin/bash
set -x
echo $UID
echo "$UID"
echo "$(id -u)"
and the result is blank!!
nothing shows up
However, when i just type "echo $UID" on terminal
it shows "1011"
is there anything i missed for bash?
Please help
UPDATED
bash version is 4.3.11 and I typed "sh test.sh" to execute.
and the result is
+ echo
+ echo
+ id -u
+ echo 1011
1011
thanks!
$UID is a Bash variable that is not set under sh, that may be why it outputs blank lines.
Try bash test.sh or make your script executable with chmod u+x test.sh, the program defined in shebang will then be used (/bin/bash)

Display a progress bar or spinner while a command is running in bash script [duplicate]

This question already has answers here:
Using Bash to display a progress indicator [duplicate]
(12 answers)
Closed 7 years ago.
I have a bash script that ends as follows:
trap "exit" INT
for ((i=0; i < $srccount; i++)); do
echo -e "\"${src[$i]}\" will be synchronized to \"${dest[$i]}\""
echo -e $'Press any key to continue or Ctrl+C to exit...\n'
read -rs -n1
#show_progress_bar()
rsync ${opt1} ${opt2} ${opt3} ${src[$i]} ${dest[$i]}
done
I need a command or a function such as show_progress_bar() that put . (a dot) in the stdout every one second while rsync command is running (or a rotating / that rotates as / - \ | sequence while rsync is running).
Is it possible? Do I need to wrote such function myself, or there is available scripts for this purpose?
It's not pretty, but it works:
~$ while true; do echo -n .; sleep 1; done & sleep 3; kill %-; wait; echo;
[1] 26255
...[1]+ Terminated while true; do
echo -n .; sleep 1;
done
(exchange the "sleep 3" for your actual work)
It works like this:
The while loop runs as a background job.
Meanwhile, your work ("sleep 3" in my example) runs in the foreground.
When the work is done, "kill %-" kills the echo loop.
Then we wait for the job to terminate, and echo a newline, just in case.
Like I said, it's not pretty. And there's probably a much better way to do it. :)
EDIT: For example, like the answer here: Using BASH to display a progress (working) indicator

executing shell command in background from script [duplicate]

This question already has answers here:
How do you run multiple programs in parallel from a bash script?
(19 answers)
Closed 1 year ago.
how can I execute a shell command in the background from within a bash script, if the command is in a string?
For example:
#!/bin/bash
cmd="nohup mycommand";
other_cmd="nohup othercommand";
"$cmd &";
"$othercmd &";
this does not work -- how can I do this?
Leave off the quotes
$cmd &
$othercmd &
eg:
nicholas#nick-win7 /tmp
$ cat test
#!/bin/bash
cmd="ls -la"
$cmd &
nicholas#nick-win7 /tmp
$ ./test
nicholas#nick-win7 /tmp
$ total 6
drwxrwxrwt+ 1 nicholas root 0 2010-09-10 20:44 .
drwxr-xr-x+ 1 nicholas root 4096 2010-09-10 14:40 ..
-rwxrwxrwx 1 nicholas None 35 2010-09-10 20:44 test
-rwxr-xr-x 1 nicholas None 41 2010-09-10 20:43 test~
Building off of ngoozeff's answer, if you want to make a command run completely in the background (i.e., if you want to hide its output and prevent it from being killed when you close its Terminal window), you can do this instead:
cmd="google-chrome";
"${cmd}" &>/dev/null & disown;
&>/dev/null sets the command’s stdout and stderr to /dev/null instead of inheriting them from the parent process.
& makes the shell run the command in the background.
disown removes the “current” job, last one stopped or put in the background, from under the shell’s job control.
In some shells you can also use &! instead of & disown; they both have the same effect. Bash doesn’t support &!, though.
Also, when putting a command inside of a variable, it's more proper to use eval "${cmd}" rather than "${cmd}":
cmd="google-chrome";
eval "${cmd}" &>/dev/null & disown;
If you run this command directly in Terminal, it will show the PID of the process which the command starts. But inside of a shell script, no output will be shown.
Here's a function for it:
#!/bin/bash
# Run a command in the background.
_evalBg() {
eval "$#" &>/dev/null & disown;
}
cmd="google-chrome";
_evalBg "${cmd}";
Also, see: Running bash commands in the background properly
This works because the it's a static variable.
You could do something much cooler like this:
filename="filename"
extension="txt"
for i in {1..20}; do
eval "filename${i}=${filename}${i}.${extension}"
touch filename${i}
echo "this rox" > filename${i}
done
This code will create 20 files and dynamically set 20 variables. Of course you could use an array, but I'm just showing you the feature :). Note that you can use the variables $filename1, $filename2, $filename3... because they were created with evaluate command. In this case I'm just creating files, but you could use to create dynamically arguments to the commands, and then execute in background.
For example you have a start program named run.sh to start it working at background do the following command line.
./run.sh &>/dev/null &

Resources