I'm trying to find a way to run multiple commands in parallel in sh and wait for it completion.
I've found that following doesn't work (sh: 1: Syntax error: ";" unexpected):
sh -c '(sleep 3 && echo 1) & ; (sleep 3 && echo 2) & ; wait'
But this syntax works as expected:
sh -c '(sleep 3 && echo 1) & ;; (sleep 3 && echo 2) & ;; wait'
But I don't understand what is the difference.
What is the meaning of ;; and when it should be used?
;; is only used in case constructs, to indicate the end of an alternative. (It's present where you have break in C.)
case $answer in
yes) echo 'yay!';;
no) echo 'boo!';;
esac
Syntactically, ; and & both mark the end of a command. A newline is equivalent to ;, in a first approximation. The difference between them is that ; or newline indicates that the command must be executed in the foreground, whereas & indicates that the command must be executed in the background.
So here you need & wait. & ; is a syntax error (you can't have an empty command). & ;; is also a syntax error; ash lets it go (as if you'd written just &), but bash complains. Evidently your sh is some ash variant (such as dash, which is /bin/sh on many Debian derivatives).
It should be used in a case statement, between cases. The issue you're having here is that both & and ; are command separators, and you should only be using one of them.
Related
I have a system that allows commands to be executed from a host to various external machines, from a bash shell script using either ssh or 'sersh', which is similar to ssh but sends commands over a serial port. (The details of these commands don't matter.)
I'm trying to chain the commands together, from one external machine to
yet a 3rd machine. I'm having a hard time figuring out how to get the shell to expand parameters only on the final machine.
function do_cmd () {
case $TRANSPORT in
ssh)
ssh -i ${SSH_KEY} ${LOGIN}#${IPADDR} "$#"
;;
serial)
sersh ${SER_LOGIN}#{SERIAL_DEV} "$#"
;;
ssh2serial)
ssh -i ${SSH_KEY} ${LOGIN}#{IPADDR} \
"sersh ${SER_LOGIN}#${SERIAL_DEV} $#"
;;
*)
echo "Unknown transport $TRANSPORT"
;;
esac
}
do_cmd "echo hello"
do_cmd "echo \"my pid is \$\$\""
do_cmd "cd /proc ; for pid in 1* ; do echo \$pid, ; done"
All three of these calls work correctly when TRANSPORT is 'ssh' or 'serial'. For the TRANSPORT 'ssh2serial', in the second call to do_cmd, the $$ is expanded prematurely (on the intermediate machine, not on the final machine). And for the third call to do_cmd, $pid ends up
being expanded to the empty string before the loop executes on the
final machine.
I thought about double-escaping the dollar signs, but the
caller doesn't know how many levels of intermediate machines there are.
Is there a way to prevent the parameter expansion on the intermediate
machine, and only do it on the final machine?
[#MadPhysicist reminds me that I'd still have to escape the variable expansions. My answer is left here below for reference but #MadPhysicist is right: my answer probably fails to answer the question asked.]
Use eval.
Try this exercise, typing the following four commands at your shell's command line. I believe that the exercise is likely to answer your question.
$ X=55
$ A='echo $(( $X + $X ))'
$ echo "$A"
$ eval "$A"
Once the exercise has shown you what the shell's built-in command eval does, you can get the effect you want by using eval on the final machine in your chain.
[Please notice that the exercise says, A='echo $(( $X + $X ))'. It does not say, A=`echo $(( $X + $X ))`. Observe the way the quotation marks slant. You want ordinary single quotes here, not backticks.]
In a DOS command prompt :
echo b | echo a
results in a being displayed, but not b. Why ? How can I pipe 2 echo in one line ?
My overall goal is to launch several process from a Haskell program, and see when each starts and ends. I would issue commands like :
echo start | myprogram | echo end
See Command Shell Overview article to learn how to run multiple commands in one line. There is a section Using multiple commands and conditional processing symbols
command1 & command2
Use to separate multiple commands on one command line. Cmd.exe runs the first command, and then the second command.
command1 && command2
Use to run the command following && only if the command preceding the symbol is successful*. Cmd.exe runs the first command, and then runs the second command only if the first command completed successfully*.
*) If a command completes an operation successfully, it returns an exit code of zero (0) or no exit code.
[quote compacted by author of answer]
...and several others.
Piping (redirecting command's output into another command's input) is not a solution here, just go with serial execution like shown above.
Wait, there's more :)
If you want to redirect the output for example to log, you would typically do:
echo a > out.txt & echo b >> out.txt & echo c >> out.txt
But there is a shorthand for that which uses parentheses mentioned in the above article:
(echo a & echo b & echo c) > out.txt
or equivalent (more readable with long commands)
> out.txt (echo a & echo b & echo c)
As you can see, this spares you from combining of > and >> which is typically one more thing to check in the command.
As Stephan suggested, I'll use :
echo start & myprogram & echo end
I am trying to run a for loop on the terminal where I want to send each iteration to background process so that all of them run simultaneously.
Following is the command running one by one
for i in *.sra; do fastq-dump --split-files $i ; done # ";" only
I have highlighted the semicolon.
To run simultaneously this works
for i in *.sra; do fastq-dump --split-files $i & done # "&" only
But this gives an error
for i in *.sra; do fastq-dump --split-files $i & ; done # "& ;"
It would be nice if some one explains what is going on here. I know this should be written in a shell script way with proper indentation, but some times I only have this command to run.
& and ; both terminate the command that precedes them.
You can't write & ; any more than you could write ; ; or & &, because the language only allows a command to be terminated once (and doesn't permit a zero-word list as a command).
Thus: for i in *.src; do fastq-dump --split-files "$i" & done is perfectly correct as-is, and does not require an additional ;.
I'm trying to use the bash select statement for a command loop. The variable in the select statement is always blank. Here is a simple script that illustrates the problem:
#!/bin/bash
select term in one two exit
do
echo you selected $term
case $term in
one ) echo one; break;;
two ) echo two; break;;
exit ) echo will exit; return;;
esac
done
Here is what happens when I run this script:
$ ./test.sh
1) one
2) two
3) exit
#? one
you selected
#? two
you selected
#? exit
you selected
#? ^D
Anyone know what I might be doing wrong? I'm on Mac OS X 10.7.3. /bin/bash --version shows: GNU bash, version 3.2.48(1)-release (x86_64-apple-darwin11)
The script works if you type in "1" or "2" rather than "one" or "two".
#jedwards gave you the immediate answer. However, if you want to protect yourself from other users having the same error, you could do something like this
select term in first second exit; do
[[ -z $term ]] && casevar=$REPLY || casevar=$term. # or, shorter, casevar=${term:-$REPLY}
case $casevar in
1|first) echo "the first option"; break ;;
2|second) echo "option no. 2"; break ;;
3|exit) echo bye; break ;;
esac
done
Note this from the bash manual:
a line is read from the standard input. If the line consists of a
number corresponding to one of the displayed words, then the value of
name is set to that word. If the line is empty, the words and prompt
are displayed again. If EOF is read, the select command completes. Any
other value read causes name to be set to null. The line read is saved
in the variable REPLY.
I am trying to run a.out lot of times from command line, but am not able to start the processes in background because bash treats it as a syntax error.
for f in `seq 20`; do ./a.out&; done //incorrect syntax for bash near '&'
How can I place & on command line so that bash doesn't complain, and I am allowed to
run these processes in background, so that I can generate load on the system.
P.S: I don't want to break it into multiple lines.
This works:
for f in `seq 20`; do ./a.out& done
& terminates a command just like ; or &&, ||, |.
This means that bash expects a command between & and ; but can't find one. Hence the error.
& is a command terminator as well as ; ; do not use both.
And use bash syntax instead of using seq, which is not available on all Unix systems.
for f in {1..20} ; do ./a.out& done
Remove the ; after a.out:
for f in `seq 20`; do ./a.out& done
Break that into multiple lines or remove the ; after the &