I am tring to understand how to use the diff command to undersand the difference between these files. Can someone please explain what this means? cases.sh was written in notepad++, cases2.sh was written in nano from within cygwin on windows7. but I cannot see the difference as I don't think it is visible? what is the advice for getting cases.sh to work, but I want to understand what is wrong with it.
$ diff cases.sh cases2.sh
1,13c1,13
< #!/bin/sh
< # Prompt user to enter a character
< echo "Please enter a letter:"
< read charac
< case $charac in
< "a"|"A") echo "You have typed a vowel!" ;;
< "e"|"E") echo "You have typed a vowel!" ;;
< "i"|"I") echo "You have typed a vowel!" ;;
< "o"|"O") echo "You have typed a vowel!" ;;
< "u"|"U") echo "You have typed a vowel!" ;;
< *) echo "You have typed a consonant!" ;;
< esac
< exit 0
\ No newline at end of file
---
> #!/bin/sh
> # Prompt user to enter a character
> echo "Please enter a letter:"
> read charac
> case $charac in
> "a"|"A") echo "You have typed a vowel!" ;;
> "e"|"E") echo "You have typed a vowel!" ;;
> "i"|"I") echo "You have typed a vowel!" ;;
> "o"|"O") echo "You have typed a vowel!" ;;
> "u"|"U") echo "You have typed a vowel!" ;;
> *) echo "You have typed a consonant!" ;;
> esac
> exit 0
cases.sh does not work, I wrote this in notepad++ and I get the error below
$ ./cases.sh
Please enter a letter:
t
': not a valid identifier `charac
./cases.sh: line 5: syntax error near unexpected token `$'in\r''
'/cases.sh: line 5: `case $charac in
cases2.sh does work but I wrote this in nano from within cygwin on windows7
$ ./cases2.sh
Please enter a letter:
t
You have typed a consonant!
try turning on "show all characters" in notepad++ ... sounds like you have windows-style newlines set for your default editor settings there. Convert the file to unix-style newlines and try running it.
You're encountering the classic linefeed problem.
Linux and the Unices (including compatibility layers like Cygwin) use a single \n (newline) to terminate each line, while the Windows family uses \r\n (carriage return, newline) for each line.
As a result, many (although not all) Unix utilities will choke on scripts written on Windows, as they contain additional, invisible, carriage returns. In your case, diff knows that cases1.sh differs from cases2.sh on those lines, but doesn't display the \r characters that actually cause the difference. Similarly, bash gets caught up on the carriage returns.
The easiest solution is enable Unix line endings in your editor of choice, and to run a tool like dos2unix on the scripts you've already written. It should be available in Cygwin's repositories.
Related
I need to code Ctrl+D in a shell script in order to exit and display the result of a code.
The goal it's instead of "bye" in my code I tried to find a solution on google and in other website to replace "bye" by a Ctrl+D code.
Can you help me please?
This is my code :
touch file.txt
while :
do
shopt -s nocasematch
read INPUT_STRING
case $INPUT_STRING in
bob)
echo "boy">>file.txt
;;
alicia)
echo "girl">>file.txt
;;
cookie)
echo "dog">>file.txt
;;
bye)
cat file.txt
break
echo " "
;;
*)
echo "unknown">>file.txt
;;
esac
done
CTRL+D is not a sequence which is sent to input but closes the input so after CTRL+D read will exit with a non null exit code and the variable INPUT_STRING will be empty. in your script read exit code is not checked. Depending on what you need, you can either check if INPUT_STRING is empty or check read exit code.
while read INPUT_STRING; do
...
done
# put the code after
cat file.txt ...
or
while :; do
...
read INPUT_STRING || break
...
done
# put the code after
cat file.txt ...
or
case $INPUT_STRING in
...
'') # to match empty INPUT_STRING (could be because of CTRL+D)
How about checking the return status of read before going into your case?
E.g.:
if [ $? -eq 1 ] ;
...
fi
I don't think you can change the behaviour of read to trap the ctrl-d in the result variable.
Caveat: This answer literally does exactly what is requested. However, keyboard typed terminal input must quote the ctrl-D and send it ( ctrl+v ctrl+d Enter ) so the script can "see" it since bash keyboard processing will "trap & grab" and process the ctrl-D without propagation otherwise.
Consequently bash preprocesses, with some predefined function, every keyboard control code:
( ^# ^A ... ^Z ^[ ^\ ^] ^^ ^_ ^? where ^ means hold ctrl then type the character)
so that the ^V predefined function "quotes" and thus escapes other ctrl'dkey functionality, so the key character code can be propagated.
aside curio:
ctrl# aka ctrlshift2 over the decades usefully creates a null character code
ctrl[ aka Esc also useful when Esc is missing or broken
Cut to the chase:
echo ^v^d>ctrl-D.txt
gedit your-script.sh ctrl-D.txt
Highlight the ctrl-D character code (it is the only) character in ctrl-D.txt and copy and paste it to replace the bye in your-script.sh.
Note bene: the embedded code is exactly 0x04 preceded by white space and postceded by a ).
The script will now terminate on receiving a ctrl-D, but here's the rub. If bash is the shell it will not receive it because bash will have already processed it. AFAIK there does not exist a unix / linux shell which does not behave like this so it is necessary to quote the ctrl-D so the shell does not grab it. (Ctrl-D was one of the early primitives "used to end text input or to exit a Unix shell".)
To quote the ctrl-D and pass it on through the shell to the script type ctrl+v ctrl+d Enter in bash. YMMV (your mileage may vary) and some other quoting method may be available in other shells.
The long answer:
To embed a control code in a shell script quote it using ctrl+v.
It is understood that the ^letter sequences in the coding examples are not to be typed literally with an ^, but as a ctrl and letter combination.
Type ctrl+v ctrl+d contiguously.
(for expediency hold the ctrl and type v then d)
Thus typing echo ctrl+vd
linuxuser#ubuntu:~$ echo ^v^d
get this:
____
|00|
|04|
but as a single "code revealed" character, not the 12 _'s |'s #'s used here to represent the result.
To embed a control code like ^D in a file such as bye use
echo ctrl+vd >bye
Thus:
linuxuser#ubuntu:~$ echo now you can see -^v^d- via cat -v >bye
linuxuser#ubuntu:~$ cat -v bye
now you can see -^D- via cat -v
reveals the ctrld character by the two characters, an ^ and a D.
For the curious:
Note that quoting the quote to quote a code has been useful.
Thus (hold) ctrl (type) vvvz (and then release ctrl) will create a ^V^Z sequence in bash.
A poignant point worth noting is that files may contain arbitrary bytes. Ergo control codes per se do not "mean anything". It is on exposure to interpretation, like bash scripts, that various sequences might have meaning as control codes. It is very useful in bash that ctrlv can "disable" bash control code interpretation by its quoting mechanism. Most particularly, quoting is useful within files themselves that are to be interpreted by bash as scripts.
Coda:
linuxuser#ubuntu:~$ echo -e "echo 'ctrl-D^v^j -^v^d-' >bye \ncat -v bye" >doit.sh
linuxuser#ubuntu:~$ ./doit.sh
prints
ctrl-D
-^D-
tested with
linuxuser#ubuntu:~$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 18.04.2 LTS
Release: 18.04
Codename: bionic
This question already has answers here:
'\r': command not found - .bashrc / .bash_profile [duplicate]
(17 answers)
Closed 5 years ago.
echo "Select your option:"
echo "1. Change ip address"
echo "2. Add route"
echo "3. Reboot"
echo "4. Exit"
read A
case $A in
1)
echo "Add Ip address"
read IP
echo "Add Netmask"
read Netid
echo "Add name of interface"
read Interface
ifconfig ${Interface} ${IP}/${Netid}
if [ $? -ne 0 ];then
echo "Ip address not configured"
fi
;;
2)
echo "Add Destination"
read dst
echo "Add Netmask"
read Netid
echo "Add Gateway"
read gw
route add $dst mask $Netid gw $gw
if [ $? -ne 0 ];then
echo "Route not added"
fi
;;
3)
reboot
;;
4)
echo "Bye"
exit 0
;;
default)
echo "Wrong selection"
exit 1
esac
Error:
[b104#b104 Downloads]$ ./NetworkUtility.sh
./NetworkUtility.sh: line 1: $'\r': command not found
Select your option:
1. Change ip address
2. Add route
3. Reboot
4. Exit
1
': not a valid identifier 7: read: `A
./NetworkUtility.sh: line 8: $'\r': command not found
./NetworkUtility.sh: line 9: syntax error near unexpected token `newline'
'/NetworkUtility.sh: line 9: `case $A in
[b104#b104 Downloads]$
It seems that you have Windows style line endings (\r\n) - you need to change them to unix style (\n). If you have dos2unix installed you could use it. You could also do it using sed or awk.
Its End of Line(EOL) conversion issue when script is written in windows using some editors like notepad, notepad++(tested).
Sed , tr may solve the issue in case of you just need to run script, however if you are developing shell script its become annoying each time first convert using, sed/tr command then run your script.
Notepad++ has an option to convert END OF LINE CONVERSION(EOL).
How to do that:
go to Edit > EOL Conversion > select Unix/OSX
Here you go, save script file and run it.
Here is screenshot of the same
The error happens, because shell doesn't understand DOS/Windows-like line endings and it expects LF instead of CRLF.
You'll need to first configure your editor to use Unix-like line endings or use dos2unix command to change it automatically, e.g.
dos2unix ./NetworkUtility.sh
Read more details at: '\r': command not found.
If you're using Vagrant, check: Windows CRLF to Unix LF Issues in Vagrant
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.
I want to write a command line tool like git which will follow the POSIX standards. It will take the options like --help or -h , --version ..etc. But i am not getting how to do it. Can anybody tell me how to do this using bash scripting. Please help me. This is something very new to me.
Example : if the name of my tool is Check-code then i want to use the tool like ;
Check-code --help
or
Check-code --version
So far as I know, "long options", like --help and --version are not POSIX standard, but GNU standard. For command-line utilities the POSIX standard says:
The arguments that consist of hyphens and single letters or digits, such as 'a', are known as "options" (or, historically, "flags").
To support POSIX short options options it is worth getting to know getopts (there are tutorials on the web), but it does not support GNU long options.
For long options you have to roll your own:
filename=default
while (( $# > 0 ))
do
opt="$1"
shift
case $opt in
--help)
helpfunc
exit 0
;;
--version)
echo "$0 version $version"
exit 0
;;
--file) # Example with an operand
filename="$1"
shift
;;
--*)
echo "Invalid option: '$opt'" >&2
exit 1
;;
*)
# end of long options
break;
;;
esac
done
You can use the 'getopts' builtin, like so:
#!/bin/bash
# Parse arguments
usage() {
echo "Usage: $0 [-h] [-v] [-f FILE]"
echo " -h Help. Display this message and quit.
echo " -v Version. Print version number and quit.
echo " -f Specify configuration file FILE."
exit
}
optspec="hvf:"
while getopts "$optspec" optchar
do
case "${optchar}" in
h)
usage
;;
v)
version
;;
f)
file=${OPTARG}
;;
*)
usage
;;
esac
done
This only works for single character options, not for long options like -help or --help. In practice, I've never found that this is a significant restriction; any script which is complex enough to require long options is probably something that I would write in a different language.
There is probably a better way to do this, but here is what I find useful:
Each argument is represented by a variable in BASH. The first argument is $1. The second is $2, and so on. Match an option string with the first argument, and if it matches run some code accordingly.
Example:
#!/bin/bash
if [ $1 == "--hello" ]
then
echo "Hello"
else
echo "Goodbye"
fi
If you code in C or C++, then use the **argv variable. **argv is a double pointer that holds a list of all arguments passed to the program (with argv[0] being the program name).
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.