I'm trying to created a Batch program that has multiple echo commands and if commands in one line. The problem is when I type a command after an echo command, it thinks it's part of the echo string and prints it to the screen instead of executing it.
For example:
if %var1% == 1 echo %var2% (right here I need to end the echo) if %var3% == 1 echo %var4%
echo.
if %var5% == 1 echo %var6% (right here I need to end the echo) if %var7% == 1 echo %var8%
I'm sure it's fairly simple, but I need to know if there's some character or command that will end a line without being interpreted as part of the message. Thanks in advance!
You need to write a string without a linefeed. echowon't to that. Instead you can use this workaround:
<nul set /p"=first "
<nul set /p"=second "
echo third
for your example:
if %var1% == 1 <nul set /p=%var2%
if %var3% == 1 echo %var4%
You can use && to separate pieces of code:
if %Var1% equ 12 echo %Var1% && if %Var2% neq 12 echo %Var2%
I tested it, it works.
I posted this answer here because it seems that my previous comment go unnoticed.
Usually an & is used to separate several individual commands in the same line; however, if the & is placed after an if or for (in the same line), then the & groups all commands in the same if or for. If you want to put several individual if or for commands (in the same line), you need to isolate each one enclosing they in parentheses:
(if %var1% == 1 echo %var2%) & if %var3% == 1 echo %var4%
(if %var5% == 1 echo %var6%) & (if %var7% == 1 echo %var8%) & echo After two previous if's
(if %varX% == 1 echo This one & echo This also) & echo Independently of previous if
Related
I've been trying to customize my Bash prompt so that it will look like
[feralin#localhost ~]$ _
with colors. I managed to get constant colors (the same colors every time I see the prompt), but I want the username ('feralin') to appear red, instead of green, if the last command had a nonzero exit status. I came up with:
\e[1;33m[$(if [[ $? == 0 ]]; then echo "\e[0;31m"; else echo "\e[0;32m"; fi)\u\e[m#\e[1;34m\h \e[0;35m\W\e[1;33m]$ \e[m
However, from my observations, the $(if ...; fi) seems to be evaluated once, when the .bashrc is run, and the result is substituted forever after. This makes the name always green, even if the last exit code is nonzero (as in, echo $?). Is this what is happening? Or is it simply something else wrong with my prompt? Long question short, how do I get my prompt to use the last exit code?
As you are starting to border on a complex PS1, you might consider using PROMPT_COMMAND. With this, you set it to a function, and it will be run after each command to generate the prompt.
You could try the following in your ~/.bashrc file:
PROMPT_COMMAND=__prompt_command # Function to generate PS1 after CMDs
__prompt_command() {
local EXIT="$?" # This needs to be first
PS1=""
local RCol='\[\e[0m\]'
local Red='\[\e[0;31m\]'
local Gre='\[\e[0;32m\]'
local BYel='\[\e[1;33m\]'
local BBlu='\[\e[1;34m\]'
local Pur='\[\e[0;35m\]'
if [ $EXIT != 0 ]; then
PS1+="${Red}\u${RCol}" # Add red if exit code non 0
else
PS1+="${Gre}\u${RCol}"
fi
PS1+="${RCol}#${BBlu}\h ${Pur}\W${BYel}$ ${RCol}"
}
This should do what it sounds like you want. Take a look a my bashrc's sub file if you want to see all the things I do with my __prompt_command function.
If you don't want to use the prompt command there are two things you need to take into account:
getting the value of $? before anything else. Otherwise it'll be overridden.
escaping all the $'s in the PS1 (so it's not evaluated when you assign it)
Working example using a variable
PS1="\$(VALU="\$?" ; echo \$VALU ; date ; if [ \$VALU == 0 ]; then echo zero; else echo nonzero; fi) "
Working example without a variable
Here the if needs to be the first thing, before any command that would override the $?.
PS1="\$(if [ \$? == 0 ]; then echo zero; else echo nonzero; fi) "
Notice how the \$() is escaped so it's not executed right away, but each time PS1 is used. Also all the uses of \$?.
Compact solution:
PS1='... $(code=${?##0};echo ${code:+[error: ${code}]})'
This approach does not require PROMPT_COMMAND (apparently this can be slower sometimes) and prints [error: <code>] if the exit code is non-zero, and nothing if it's zero:
... > false
... [error: 1]> true
... >
Change the [error: ${code}] part depending on your liking, with ${code} being the non-zero code to print.
Note the use of ' to ensure the inline $() shell gets executed when PS1 is evaluated later, not when the shell is started.
As bonus, you can make it colorful in red by adding \e[01;31m in front and \e[00m after to reset:
PS1='... \e[01;31m$(code=${?##0};echo ${code:+[error: ${code}]})\e[00m'
--
How it works:
it uses bash parameter substitution
first, the ${?##0} will read the exit code $? of the previous command
the ## will remove any 0 pattern from the beginning, effectively making a 0 result an empty var (thanks #blaskovicz for the trick)
we assign this to a temporary code variable as we need to do another substitution, and they can't be nested
the ${code:+REPLACEMENT} will print the REPLACEMENT part only if the variable code is set (non-empty)
this way we can add some text and brackets around it, and reference the variable again inline: [error: ${code}]
I wanted to keep default Debian colors, print the exact code, and only print it on failure:
# Show exit status on failure.
PROMPT_COMMAND=__prompt_command
__prompt_command() {
local curr_exit="$?"
local BRed='\[\e[0;91m\]'
local RCol='\[\e[0m\]'
PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u#\h\[\033[00m\]:\[\033[01;34m\]\w\[\033[00m\]\$ '
if [ "$curr_exit" != 0 ]; then
PS1="[${BRed}$curr_exit${RCol}]$PS1"
fi
}
The following provides a leading green check mark when the exit code is zero and a red cross in all other cases. The remainder is a standard colorized prompt. The printf statements can be modified to present the two states that were originally requested.
PS1='$(if [ $? -eq 0 ]; then printf "\033[01;32m""\xE2\x9C\x93"; else printf "\033[01;31m""\xE2\x9C\x95"; fi) \[\e[00;32m\]\u#\h\[\e[00;30m\]:\[\e[01;33m\]\w\[\e[01;37m\]\$ '
Why didn't I think about that myself? I found this very interesting and added this feature to my 'info-bar' project. Eyes will turn red if the last command failed.
#!/bin/bash
eyes=(O o ∘ ◦ ⍤ ⍥) en=${#eyes[#]} mouth='_'
face () { # gen random face
[[ $error -gt 0 ]] && ecolor=$RED || ecolor=$YLW
if [[ $1 ]]; then printf "${eyes[$[RANDOM%en]]}$mouth${eyes[$[RANDOM%en]]}"
else printf "$ecolor${eyes[$[RANDOM%en]]}$YLW$mouth$ecolor${eyes[$[RANDOM%en]]}$DEF"
fi
}
info () { error=$?
[[ -d .git ]] && { # If in git project folder add git status to info bar output
git_clr=('GIT' $(git -c color.ui=always status -sb)) # Colored output 4 info
git_tst=('GIT' $(git status -sb)) # Simple output 4 test
}
printf -v line "%${COLUMNS}s" # Set border length
date=$(printf "%(%a %d %b %T)T") # Date & time 4 test
test=" O_o $PWD ${git_tst[*]} $date o_O " # Test string
step=$[$COLUMNS-${#test}]; [[ $step -lt 0 ]] && step=0 # Count spaces
line="$GRN${line// /-}$DEF\n" # Create lines
home="$BLD$BLU$PWD$DEF" # Home dir info
date="$DIM$date$DEF" # Colored date & time
#------+-----+-------+--------+-------------+-----+-------+--------+
# Line | O_o |homedir| Spaces | Git status | Date| o_O | Line |
#------+-----+-------+--------+-------------+-----+-------+--------+
printf "$line $(face) $home %${step}s ${git_clr[*]} $date $(face) \n$line" # Final info string
}
PS1='${debian_chroot:+($debian_chroot)}\n$(info)\n$ '
case "$TERM" in xterm*|rxvt*)
PS1="\[\e]0;${debian_chroot:+($debian_chroot)} $(face 1) \w\a\]$PS1";;
esac
Improved demure answer:
I think this is important because the exit status is not always 0 or 1.
if [ $EXIT != 0 ]; then
PS1+="${Red}${EXIT}:\u${RCol}" # Add red if exit code != 0
else
PS1+="${Gre}${EXIT}:\u${RCol}" # Also displays exit status
fi
To preserve the original prompt format (not just colors),
you could append following to the end of file ~/.bashrc:
PS1_ORIG=$PS1 # original primary prompt value
PROMPT_COMMAND=__update_prompt # Function to be re-evaluated after each command is executed
__update_prompt() {
local PREVIOUS_EXIT_CODE="$?"
if [ $PREVIOUS_EXIT_CODE != 0 ]; then
local RedCol='\[\e[0;31m\]'
local ResetCol='\[\e[0m\]'
local replacement="${RedCol}\u${ResetCol}"
# Replace username color
PS1=${PS1_ORIG//]\\u/]$replacement}
## Alternative: keep same colors, append exit code
#PS1="$PS1_ORIG[${RedCol}error=$PREVIOUS_EXIT_CODE${ResetCol}]$ "
else
PS1=$PS1_ORIG
fi
}
See also the comment about the alternative approach that preserves username color and just appends an error code in red to the end of the original prompt format.
You can achieve a similar result to include a colored (non-zero) exit code in a prompt, without using subshells in the prompt nor prompt_command.
You color the exit code portion of the prompt, while having it only appear when non-zero.
Core 2$ section of the prompt: \\[\\033[0;31;4m\\]\${?#0}\\[\\033[0;33m\\]\$ \\[\\033[0m\\]
Key elements:
return code, if not 0: \${?#0} (specificly "removes prefix of 0")
change color without adding to calculated prompt-width: \\[\\033[0;31m\\]
\\[ - begin block
\\033 - treat as 0-width, in readline calculations for cmdline editing
[0;31;4m - escape code, change color, red fg, underline
\\] - end block
Components:
\\[\\033[0;31;4m\\] - set color 0;31m fg red, underline
\${?#0} - display non-zero status (by removing 0 prefix)
\\[\\033[0;33m\\] - set color 0;33m fg yellow
\$ - $ or # on EUID
\\[\\033[0m\\] - reset color
The full PS1 I use (on one host):
declare -x PS1="\\[\\033[0;35m\\]\\h\\[\\033[1;37m\\] \\[\\033[0;37m\\]\\w \\[\\033[0;33m\\]\\[\\033[0;31;4m\\]\${?#0}\\[\\033[0;33m\\]\$ \\[\\033[0m\\]"
Note: this addresses a natural extension to this question, in a more enduring way then a comment.
Bash
function my_prompt {
local retval=$?
local field1='\u#\h'
local field2='\w'
local field3='$([ $SHLVL -gt 1 ] && echo \ shlvl:$SHLVL)$([ \j -gt 0 ] && echo \ jobs:\j)'"$([ ${retval} -ne 0 ] && echo \ exit:$retval)"
local field4='\$'
PS1=$'\n'"\e[0;35m${field1}\e[m \e[0;34m${field2}\e[m\e[0;31m${field3}\e[m"$'\n'"\[\e[0;36m\]${field4}\[\e[m\] "
}
PROMPT_COMMAND="my_prompt; ${PROMPT_COMMAND}"
Zsh
PROMPT=$'\n''%F{magenta}%n#%m%f %F{blue}%~%f%F{red}%(2L. shlvl:%L.)%(1j. jobs:%j.)%(?.. exit:%?)%f'$'\n''%F{cyan}%(!.#.$)%f '
Images of prompt
This is my first time trying scripting and I'm trying to create a small program that does a simple division and mod in a loop, and then calculates the average of the mod results.
This is what I have tried in Linux .sh, but how could I make it compatible with Windows .bat? Your help is very appreciated.
echo "enter first number:"
read first_num
echo “enter second number:”
read second_num
while [ first_num && second_num != 999 ]
do
if [ second_num != 0 ]; then
echo "Enter first number:"
read first_num
echo"Enter second number:"
read second_num
echo first_num "/" second_num "=" $((first_num / second_ num)) >> file.txt
else
echo "ERROR. Cannot divide by 0. Enter another number:"
fi
done
if [ first_num == 999 || second_num == 999 ]; then
echo "You have exited the loop."
fi
#Mod 5 of numbers 1-100:
for i in {1...100}
do
result=$((i % 5))
echo i + "%5=" + result >> file.txt
done
#Average of results:
int sum=0
for (( i=1; i<101; i=i+1 ))
do
sum=sum+$((i % 5))
average=$((sum/100))
echo average
echo average >> file.txt
done
echo "enter first number:"
read first_num
becomes
set /p first_num="Enter first number "
while [ first_num && second_num != 999 ]
don't have a WHILE - have to wire it. Think clubs and rocks.
:loop
if %first_num%==999 goto endloop
if %second_num%==999 goto endloop
...
goto loop
:endloop
:name is a label, %var% retrieves contents of var - is always a string enclose in quotes if the string includes spaces.
if [ second_num != 0 ]; then
translated is
if NOT %second_num%==0 ( ...things... ) else (...other things...)
or, of course
if %second_num%==0 ( ...other things... ) else (...things...)
Quirk: (one of many) : the first open-parenthesis MUST occur on the same physical line as the IF and the ELSE must be on the same physical line as the ) of the on-true statemet sequence.
echo first_num "/" second_num "=" $((first_num / second_ num)) >> file.txt
Can't do a calculation in an echo
set /a result=(first_num / second_ num)
OR
set /a result=(%first_num% / %second_ num%)
SET /A applies the results of an arithmetic expression - later addition and more C-like semantics
then
echo %first_num% / %second_num% = %result% >> file.txt
purely stringing the elements together.
Next there is a small problem. During the parsing process, any %var% is replaced by its PARSE-TIME value and THEN the line is executed. Consequently, the ECHO line above would show the values as they stood when the IF statement was entered, not after the calculations.
Two cures:
You can use SETLOCAL ENABLEDELATEDEXPANSION to switch the interpreter mode. In DELAYEDEXPANSION mode, !var! may be used to retrieve the RUN-TIME value of var. A SETLOCAL is terminated by an ENDLOCAL or by reaching END-OF-FILE in the same context. Any environment changes after a SETLOCAL are undone by an ENDLOCAL which is why it's often performed immediately after the #echo off - keeps the environment clean.
second cure is to use a subroutine. CALL :SUB (the colon means 'internal subroutine - start label is in this batchfile'. Omitting it means 'this is an external executable') The CALL creates a new context, copying the then-existing environment variables, so
:sub
echo %first_num% / %second_num% = %result% >> file.txt
goto :eof
will display the variables from the environment as it stood when a CALL :SUB was executed.
Note that GOTO :EOF (the colon is REQUIRED means 'go to physical end-of-file' - the label EOF should not be declared...
(beware also flow-through to subroutines normally placed at the end of batchfiles. A judicious GOTO :EOF ensures that flow-through does not occur...
#Mod 5 of numbers 1-100:
The comments indicator is officially REM
rem Mod 5 of numbers 1-100:
BUT
::Mod 5 of numbers 1-100:
is often used as it's easier to type BUT since it's actually a misuse of a label, it is actually a label, and labels can't be used in a compound statement, you you can't use it within the parentheses of IF ... (...) else (...) or FOR...DO (...)
for i in {1...100}
becomes
for /L %%i in (1,1,100) do (
The metavariable %%i IS case-sensitive and a single character. in a FOR /L the elements are (start,step,end) - see FOR /? from the prompt (or generally command /? from the prompt) for docco...
result=$((i % 5))
becomes
set /a result=%%i %% 5
/a because the RHS is an arithmetic expression to be evaluated; %% 5 because % escapes %, the space is irrelevant and the processor needs to know that the MOD operator % is being used, not %5 (the fifth command-line argument)
int sum=0
No such thing as types; all environment variables are strings and only interpreted as integers by the set /a statement
so - that's about all there is to it...
Hope you're looking for this:
#echo off
setlocal enabledelayedexpansion
> file.txt type nul
:loopInput
echo enter first number:
set /p first_num=
echo enter second number:
set /p second_num=
if !first_num! neq 0 (
if !second_num! neq 999 (
if !second_num! equ 0 (
echo ERROR. Cannot divide by 0. Enter another number:
goto loopInput
)
goto loopDiv
)
)
goto :skipDiv
:loopDiv
set /a division=first_num/second_num
>> file.txt echo !first_num! / !second_num! = !division!
goto loopInput
:skipDiv
echo You have exited the div loop.
>> file.txt echo Mod 5 of numbers 1-100:
for /l %%a in (1,1,100) do (
set /a mod=%%a%%5
>> file.txt echo %%a %% 5 = !mod!
)
set sum=0
for /l %%a in (1,1,100) do (
set /a sum+=%%a%%5
)
set /a average=sum/100
>> file.txt echo Average of results: !average!
I am trying to make a switch statement to work in tcsh but I am not sure why it is not working. I am displaying a menu on the screen and if the option is selected it shows the price and then goes back to the top and repeats until the exit option is selected.
#!/bin/csh
clear
echo -n "Petes A Pizza "
echo -n " Menu "
echo -n " "
echo -n " Make a selection "
echo -n " "
echo -n " A. Speciality Pizza "
echo -n " B. Veggi Lovers Pizza "
echo -n " C. Meat Lovers Pizza "
echo -n " D. Hawaiian Pizza "
echo -n " E. Cheese Pizza "
echo -n " F. Exit "
set a = $<
switch ($a)
case [A] :
set A = ((7.99 + 0.07))
echo $A
sleep 5
goto top
case [B] : #they choose option 2
set B = ((8.99 * 0.07) + 8.99)
echo $B
sleep 5
goto top
case [C] : #they choose option 3
set C = ((6.99 * 0.07) + 6.99)
echo $C
sleep 5
goto top
case [D] : #they choose option 4
set D = ((8.49 * 0.07) + 8.49)
echo $D
sleep 5
goto top
case [E] : #they choose option 5
set E = ((3.99 * 0.07) + 3.99)
echo $E
sleep 5
case [F] :
exit 0
breaksw
endsw
end
Here are a few suggestions that should be enough to help you get it working.
Change #!/bin/csh to #!bin/csh -f. This tells the shell not to read your ~/.cshrc file, which saves time and can avoid confusion. (If you accidentally write code that depends on aliases you've defined in your .cshrc, for example, your script won't work for anyone else.)
If you must clear the screen, the clear command is the way to do it -- but why? If I want to clear my screen before running your script, I'll do it myself, thank you very much. If I have information on my screen that I don't want to lose, I'll be annoyed when your script decides to erase it for me.
Change all the echo -ns to just echo. The -n option tells echo to print its output without a trailing newline; your entire menu will be printed on one line.
The square brackets in your case labels are unnecessary. case A : means the same thing as case [A] :. Note that you're requiring the user to provide input in upper case, which may be inconvenient.
set A = ((7.99 + 0.07))
...
set B = ((8.99 * 0.07) + 8.99)
These are inconsistent. It looks like you're trying to compute a base price plus 7% sales tax. For case B, a simpler expression for that is 8.99 * 1.07.
csh doesn't recognize this (( ... )) syntax; I wonder where you got the idea that it does. csh can do arithmetic using the # command:
# x = 2 + 2
# x ++
but it only operates on integers. The bc command can do floating-point calculations. You could write something like:
set B = `echo 'scale=5; 1.07 * 8.99' | bc`
Or, more simply:
set B = `echo '1.07 * 8.99' | bc -l
but bc -l may give you more digits than you want. man bc for more information on the bc command, its syntax, and how it works. Remember that the values of csh variables are strings, not numbers.
(I'm not sure bc is the best tool for this job.)
Finally, csh is not the best language for writing scripts. I've been using it for more years than I care to admit, and I sometimes have to resort to trial and error to find out how a given piece of syntax will behave; the syntax is poorly defined in many cases, and the man page doesn't always clear things up.
Suggested reading: "Csh Programming Considered Harmful", by Tom Christiansen.
Is there a "goto" statement in bash ? I know It is considered bad practice, but I need specifically "goto".
If you are using it to skip part of a large script for debugging (see Karl Nicoll's comment), then if false could be a good option (not sure if "false" is always available, for me it is in /bin/false):
# ... Code I want to run here ...
if false; then
# ... Code I want to skip here ...
fi
# ... I want to resume here ...
The difficulty comes in when it's time to rip out your debugging code. The "if false" construct is pretty straightforward and memorable, but how do you find the matching fi? If your editor allows you to block indent, you could indent the skipped block (then you'll want to put it back when you're done). Or a comment on the fi line, but it would have to be something you'll remember, which I suspect will be very programmer-dependent.
No, there is not; see §3.2.4 "Compound Commands" in the Bash Reference Manual for information about the control structures that do exist. In particular, note the mention of break and continue, which aren't as flexible as goto, but are more flexible in Bash than in some languages, and may help you achieve what you want. (Whatever it is that you want . . .)
It indeed may be useful for some debug or demonstration needs.
I found that Bob Copeland solution http://bobcopeland.com/blog/2012/10/goto-in-bash/ elegant:
#!/bin/bash
# include this boilerplate
function jumpto
{
label=$1
cmd=$(sed -n "/$label:/{:a;n;p;ba};" $0 | grep -v ':$')
eval "$cmd"
exit
}
start=${1:-"start"}
jumpto $start
start:
# your script goes here...
x=100
jumpto foo
mid:
x=101
echo "This is not printed!"
foo:
x=${x:-10}
echo x is $x
results in:
$ ./test.sh
x is 100
$ ./test.sh foo
x is 10
$ ./test.sh mid
This is not printed!
x is 101
You can use case in bash to simulate a goto:
#!/bin/bash
case bar in
foo)
echo foo
;&
bar)
echo bar
;&
*)
echo star
;;
esac
produces:
bar
star
If you're testing/debugging a bash script, and simply want to skip forwards past one or more sections of code, here is a very simple way to do it that is also very easy to find and remove later (unlike most of the methods described above).
#!/bin/bash
echo "Run this"
cat >/dev/null <<GOTO_1
echo "Don't run this"
GOTO_1
echo "Also run this"
cat >/dev/null <<GOTO_2
echo "Don't run this either"
GOTO_2
echo "Yet more code I want to run"
To put your script back to normal, just delete any lines with GOTO.
We can also prettify this solution, by adding a goto command as an alias:
#!/bin/bash
shopt -s expand_aliases
alias goto="cat >/dev/null <<"
goto GOTO_1
echo "Don't run this"
GOTO_1
echo "Run this"
goto GOTO_2
echo "Don't run this either"
GOTO_2
echo "All done"
Aliases don't usually work in bash scripts, so we need the shopt command to fix that.
If you want to be able to enable/disable your goto's, we need a little bit more:
#!/bin/bash
shopt -s expand_aliases
if [ -n "$DEBUG" ] ; then
alias goto="cat >/dev/null <<"
else
alias goto=":"
fi
goto '#GOTO_1'
echo "Don't run this"
#GOTO1
echo "Run this"
goto '#GOTO_2'
echo "Don't run this either"
#GOTO_2
echo "All done"
Then you can do export DEBUG=TRUE before running the script.
The labels are comments, so won't cause syntax errors if disable our goto's (by setting goto to the ':' no-op), but this means we need to quote them in our goto statements.
Whenever using any kind of goto solution, you need to be careful that the code you're jumping past doesn't set any variables that you rely on later - you may need to move those definitions to the top of your script, or just above one of your goto statements.
Although others have already clarified that there is no direct goto equivalent in bash (and provided the closest alternatives such as functions, loops, and break), I would like to illustrate how using a loop plus break can simulate a specific type of goto statement.
The situation where I find this the most useful is when I need to return to the beginning of a section of code if certain conditions are not met. In the example below, the while loop will run forever until ping stops dropping packets to a test IP.
#!/bin/bash
TestIP="8.8.8.8"
# Loop forever (until break is issued)
while true; do
# Do a simple test for Internet connectivity
PacketLoss=$(ping "$TestIP" -c 2 | grep -Eo "[0-9]+% packet loss" | grep -Eo "^[0-9]")
# Exit the loop if ping is no longer dropping packets
if [ "$PacketLoss" == 0 ]; then
echo "Connection restored"
break
else
echo "No connectivity"
fi
done
This solution had the following issues:
Indiscriminately removes all code lines ending in a :
Treats label: anywhere on a line as a label
Here's a fixed (shell-check clean and POSIX compatible) version:
#!/bin/sh
# GOTO for bash, based upon https://stackoverflow.com/a/31269848/5353461
goto() {
label=$1
cmd=$(sed -En "/^[[:space:]]*#[[:space:]]*$label:[[:space:]]*#/{:a;n;p;ba};" "$0")
eval "$cmd"
exit
}
start=${1:-start}
goto "$start" # GOTO start: by default
#start:# Comments can occur after labels
echo start
goto end
# skip: # Whitespace is allowed
echo this is usually skipped
# end: #
echo end
There is one more ability to achieve a desired results: command trap. It can be used to clean-up purposes for example.
There is no goto in bash.
Here is some dirty workaround using trap which jumps only backwards:)
#!/bin/bash -e
trap '
echo I am
sleep 1
echo here now.
' EXIT
echo foo
goto trap 2> /dev/null
echo bar
Output:
$ ./test.sh
foo
I am
here now.
This shouldn't be used in that way, but only for educational purposes. Here is why this works:
trap is using exception handling to achieve the change in code flow.
In this case the trap is catching anything that causes the script to EXIT. The command goto doesn't exist, and hence throws an error, which would ordinarily exit the script. This error is being caught with trap, and the 2>/dev/null hides the error message that would ordinarily be displayed.
This implementation of goto is obviously not reliable, since any non-existent command (or any other error, for that manner), would execute the same trap command. In particular, you cannot choose which label to go-to.
Basically in real scenario you don't need any goto statements, they're redundant as random calls to different places only make your code difficult to understand.
If your code is invoked many times, then consider to use loop and changing its workflow to use continue and break.
If your code repeats it-self, consider writing the function and calling it as many times as you want.
If your code needs to jump into specific section based on the variable value, then consider using case statement.
If you can separate your long code into smaller pieces, consider moving it into separate files and call them from the parent script.
I found out a way to do this using functions.
Say, for example, you have 3 choices: A, B, and C. A and Bexecute a command, but C gives you more info and takes you to the original prompt again. This can be done using functions.
Note that since the line containg function demoFunction is just setting up the function, you need to call demoFunction after that script so the function will actually run.
You can easily adapt this by writing multiple other functions and calling them if you need to "GOTO" another place in your shell script.
function demoFunction {
read -n1 -p "Pick a letter to run a command [A, B, or C for more info] " runCommand
case $runCommand in
a|A) printf "\n\tpwd being executed...\n" && pwd;;
b|B) printf "\n\tls being executed...\n" && ls;;
c|C) printf "\n\toption A runs pwd, option B runs ls\n" && demoFunction;;
esac
}
demoFunction
This is a small correction of the Judy Schmidt script put up by Hubbbitus.
Putting non-escaped labels in the script was problematic on the machine and caused it to crash. This was easy enough to resolve by adding # to escape the labels. Thanks to Alexej Magura and access_granted for their suggestions.
#!/bin/bash
# include this boilerplate
function goto {
label=$1
cmd=$(sed -n "/$#label#:/{:a;n;p;ba};" $0 | grep -v ':$')
eval "$cmd"
exit
}
start=${1:-"start"}
goto $start
#start#
echo "start"
goto bing
#boom#
echo boom
goto eof
#bang#
echo bang
goto boom
#bing#
echo bing
goto bang
#eof#
echo "the end mother-hugger..."
A simple searchable goto for the use of commenting out code blocks when debugging.
GOTO=false
if ${GOTO}; then
echo "GOTO failed"
...
fi # End of GOTO
echo "GOTO done"
Result is-> GOTO done
My idea for creating something like "goto" is to use select with case and assign a variable, which I then check in an if statement. Not perfect, but may help in some cases
Example:
#!/usr/bin/env bash
select goto in Ubuntu Debian Quit ; do
case $goto in
Ubuntu) { CHOICE="Ubuntu" ; break ; } ;;
Debian) { CHOICE="Debian" ; break ; } ;;
Quit) { echo "Bye" ; exit ; } ;;
*) { echo "Invalid selection, please try again..." ; } ;;
esac
done
if [ "$CHOICE" == "Ubuntu" ]; then
echo "I'm in Ubuntu"
fi
if [ "$CHOICE" == "Debian" ]; then
echo "I'm in Debian"
fi
Why don't anyone just use functions directly ?
BTW functions are easier to deal with than making a new thing
My style :
#!/bin/bash
# Your functions
function1 ()
{
commands
}
function2 ()
{
commands
}
:
:
functionn ()
{
commands
}
# Execute 1 to n in order
for i in {1..n}
do
function$i
done
# with conditions
for i in {1..n}
do
[ condition$i ] && function$i
done
# Random order
function1
functionn
function5
:
:
function3
Example for above style :
#!/bin/bash
# Your functions
function1 ()
{
echo "Task 1"
}
function2 ()
{
echo "Task 2"
}
function3 ()
{
echo "Task 3"
}
function1
function3
function2
Output :
Task 1
Task 3
Task 2
Drawbacks :
Script in an organized way.
Less problems and not prone to errors.
You can make function inside a existing function.
Move back and forth without any problems.
1) Is there any built-in which can tell me if a variable's contents contain only uppercase letters?
2) is there any way to see if a variable contains a string? For example, I'd like to see if the variable %PATH% contains Ruby.
For part 1, findstr is the answer. You can just use the regex feature along with errorlevel:
> set xxokay=ABC
> set xxbad=AB1C
> echo %xxokay%|findstr /r "^[A-Z]*$" >nul:
> echo %errorlevel%
0
> echo %xxbad%|findstr /r "^[A-Z]*$" >nul:
> echo %errorlevel%
1
It's important in this case that you do not have a space between the echo %xxokay% and the pipe character |, since that will result in a space being output which is not one of your acceptable characters.
For part 2, findstr is also the answer (/i is ignore case which may be what you want - leave it off if case must match):
> set xxruby=somewhere;c:\ruby;somewhere_else
> set xxnoruby=somewhere;somewhere_else
> echo %xxruby%|findstr /i ruby >nul:
> echo %errorlevel%
0
> echo %xxnoruby%|findstr /i ruby >nul:
> echo %errorlevel%
1
You can then use:
if %errorlevel%==1 goto :label
to change the behaviour of your script in both cases.
For example, the code segment for the ruby check could be something like:
:ruby_check
echo %yourvar%|findstr /i ruby >nul:
if %errorlevel%==1 goto :ruby_check_not_found
:ruby_check_found
:: ruby was found
goto :ruby_check_end
:ruby_check_not_found:
:: ruby was NOT found
:ruby_check_end
this is not a batch solution (cmd.exe), but an easier alternative using vbscript, which by default is already installed on your system.
Set objArgs = WScript.Arguments
var=objArgs(0)
check=var
If check=UCase(var) Then
WScript.Echo "String contains all uppercase"
Else
WScript.Echo "String doesn't contain all uppercase"
End If
' to check string contains substring
mystring="This is my ruby string"
check="ruby"
If InStr(mystring,check)>0 Then
WScript.Echo "string contains ruby"
End If
save the file as myscript.vbs and run it like this
C:\test>cscript //nologo myscript.vbs abC
String doesn't contain all uppercase
string contains ruby
C:\test>cscript //nologo myscript.vbs ABCD
String contains all uppercase
string contains ruby
using batch(cmd.exe) for string manipulation is the last thing you would want to do, unless you are absolutely restricted. Otherwise, use the more appropriate tool for the job.