Block Comments in a Shell Script - shell

Is there a simple way to comment out a block of code in a shell script?

In bash:
#!/bin/bash
echo before comment
: <<'END'
bla bla
blurfl
END
echo after comment
The ' and ' around the END delimiter are important, otherwise things inside the block like for example $(command) will be parsed and executed.
For an explanation, see this and this question.

There is no block comment on shell script.
Using vi (yes, vi) you can easily comment from line n to m
<ESC>
:10,100s/^/#/
(that reads, from line 10 to 100 substitute line start (^) with a # sign.)
and un comment with
<ESC>
:10,100s/^#//
(that reads, from line 10 to 100 substitute line start (^) followed by # with noting //.)
vi is almost universal anywhere where there is /bin/sh.

Use : ' to open and ' to close.
For example:
: '
This is a
very neat comment
in bash
'
This is from Vegas's example found here

You can use:
if [ 1 -eq 0 ]; then
echo "The code that you want commented out goes here."
echo "This echo statement will not be called."
fi

The following should work for sh,bash, ksh and zsh.
The blocks of code to be commented can be put inside BEGINCOMMENT and ENDCOMMENT:
[ -z $BASH ] || shopt -s expand_aliases
alias BEGINCOMMENT="if [ ]; then"
alias ENDCOMMENT="fi"
BEGINCOMMENT
echo "This line appears in a commented block"
echo "And this one too!"
ENDCOMMENT
echo "This is outside the commented block"
Executing the above code would result in:
This is outside the commented block
In order to uncomment the code blocks thus commented, say
alias BEGINCOMMENT="if : ; then"
instead of
alias BEGINCOMMENT="if [ ]; then"
in the example above.

if you can dodge the single quotes:
__='
blah blah comment.
'

In Vim:
go to first line of block you want to comment
shift-V (enter visual mode), up down highlight lines in block
execute the following on selection :s/^/#/
the command will look like this:
:'<,'>s/^/#
hit enter
e.g.
shift-V
jjj
:s/^/#
<enter>

You could use Vi/Vim's Visual Block mode which is designed for stuff like this:
Ctrl-V
Highlight first element in rows you want commented
Shift-i
#
esc
Uncomment would be:
Ctrl-V
Highlight #'s
d
l
This is vi's interactive way of doing this sort of thing rather than counting or reading line numbers.
Lastly, in Gvim you use ctrl-q to get into Visual Block mode rather than ctrl-v (because that's the shortcut for paste).

In all honesty, why so much overengineering...
I consider it really a bad practice to write active code for generating passive code.
My solution: most editors have block select mode. Just use it to add # to all lines you want to comment out.
What's the big deal...
Notepad example:
To create: Alt - mousedrag down, press #.
To delete: Alt-mousedrag down, shift-right arrow, delete.

A variation on the here-doc trick in the accepted answer by sunny256 is to use the Perl keywords for comments. If your comments are actually some sort of documentation, you can then start using the Perl syntax inside the commented block, which allows you to print it out nicely formatted, convert it to a man-page, etc.
As far as the shell is concerned, you only need to replace 'END' with '=cut'.
echo "before comment"
: <<'=cut'
=pod
=head1 NAME
podtest.sh - Example shell script with embedded POD documentation
etc.
=cut
echo "after comment"
(Found on "Embedding documentation in shell script")

You can put the code to comment inside a function. A good thing about this is you can "uncomment" by calling the function just after the definition.
Unless you plan to "uncomment" by calling the function, the text inside the function does not have to be syntactically correct.
ignored() {
echo this is comment
echo another line of comment
}
Many GUI editors will allow you to select a block of text, and press "{" to automatically put braces around the selected block of code.

Let's combine the best of all of these ideas and suggestions.
alias _CommentBegin_=": <<'_CommentEnd_'"
as has been said, the single quote is very important, in that without them
$(commandName) and ${varName} would get evaluated.
You would use it as:
_CommentBegin_
echo "bash code"
or
none code can be in here
_CommentEnd_
The alias makes the usage more obvious and better looking.

I like a single line open and close:
if [ ]; then ##
...
...
fi; ##
The '##' helps me easily find the start and end to the block comment. I can stick a number after the '##' if I've got a bunch of them. To turn off the comment, I just stick a '1' in the '[ ]'. I also avoid some issues I've had with single-quotes in the commented block.

Another mode is:
If your editor HAS NO BLOCK comment option,
Open a second instance of the editor (for example File=>New File...)
From THE PREVIOUS file you are working on, select ONLY THE PART YOU WANT COMMENT
Copy and paste it in the window of the new temporary file...
Open the Edit menu, select REPLACE and input as string to be replaced '\n'
input as replace string: '\n#'
press the button 'replace ALL'
DONE
it WORKS with ANY editor

In vscode ctrl+K+C (ctrl+K+U to uncomment).

Related

Create file with content in one line of bash

I wish to create a single file with some contents known to me.
How do I do this in couple lines of bash?
this command will be used inside of a single script, so it should create file, add text, save, and quit automatically by itself without human intervention.
I know that
cat >> some.text
type some stuff
ctrl + D
will work. But is there a pure command line way of doing it?
Thanks
Use a "here document":
cat >> some.text << 'END'
some stuff here
more stuff
END
The delimiter (here END) is an arbitrary word. Quoting this delimiter after the << will ensure that no expansion is performed on the contents.
You could also do the following:
echo 'some stuff' > your/file.txt
For multiline, here's another example:
printf "some stuff\nmore stuff" >> your/file.txt
For making it multiline its also possilbe to echo in "execution mode":
echo -e "line1\nline2" > /tmp/file
so the \n will make a carriage return.
Great answer from #that-other-guy, also important to note that you can include the directory of the file in there and not to forget your bin/bash stuff at the start, and that it works for more than just text files.
See below my example for yaml files. And remember to make your bash files executable after with: chmod u+x fileName.sh
#!/usr/bin/bash
cat >> ~/dir/newDir/yamlFiles/testing.yaml << 'END'
service:
- testing
testing:
setOpts:
podCount: 2
END

Way to create multiline comments in Bash?

I have recently started studying shell script and I'd like to be able to comment out a set of lines in a shell script. I mean like it is in case of C/Java :
/* comment1
comment2
comment3
*/`
How could I do that?
Use : ' to open and ' to close.
For example:
: '
This is a
very neat comment
in bash
'
Multiline comment in bash
: <<'END_COMMENT'
This is a heredoc (<<) redirected to a NOP command (:).
The single quotes around END_COMMENT are important,
because it disables variable resolving and command resolving
within these lines. Without the single-quotes around END_COMMENT,
the following two $() `` commands would get executed:
$(gibberish command)
`rm -fr mydir`
comment1
comment2
comment3
END_COMMENT
Note: I updated this answer based on comments and other answers, so comments prior to May 22nd 2020 may no longer apply. Also I noticed today that some IDE's like VS Code and PyCharm do not recognize a HEREDOC marker that contains spaces, whereas bash has no problem with it, so I'm updating this answer again.
Bash does not provide a builtin syntax for multi-line comment but there are hacks using existing bash syntax that "happen to work now".
Personally I think the simplest (ie least noisy, least weird, easiest to type, most explicit) is to use a quoted HEREDOC, but make it obvious what you are doing, and use the same HEREDOC marker everywhere:
<<'###BLOCK-COMMENT'
line 1
line 2
line 3
line 4
###BLOCK-COMMENT
Single-quoting the HEREDOC marker avoids some shell parsing side-effects, such as weird subsitutions that would cause crash or output, and even parsing of the marker itself. So the single-quotes give you more freedom on the open-close comment marker.
For example the following uses a triple hash which kind of suggests multi-line comment in bash. This would crash the script if the single quotes were absent. Even if you remove ###, the FOO{} would crash the script (or cause bad substitution to be printed if no set -e) if it weren't for the single quotes:
set -e
<<'###BLOCK-COMMENT'
something something ${FOO{}} something
more comment
###BLOCK-COMMENT
ls
You could of course just use
set -e
<<'###'
something something ${FOO{}} something
more comment
###
ls
but the intent of this is definitely less clear to a reader unfamiliar with this trickery.
Note my original answer used '### BLOCK COMMENT', which is fine if you use vanilla vi/vim but today I noticed that PyCharm and VS Code don't recognize the closing marker if it has spaces.
Nowadays any good editor allows you to press ctrl-/ or similar, to un/comment the selection. Everyone definitely understands this:
# something something ${FOO{}} something
# more comment
# yet another line of comment
although admittedly, this is not nearly as convenient as the block comment above if you want to re-fill your paragraphs.
There are surely other techniques, but there doesn't seem to be a "conventional" way to do it. It would be nice if ###> and ###< could be added to bash to indicate start and end of comment block, seems like it could be pretty straightforward.
After reading the other answers here I came up with the below, which IMHO makes it really clear it's a comment. Especially suitable for in-script usage info:
<< ////
Usage:
This script launches a spaceship to the moon. It's doing so by
leveraging the power of the Fifth Element, AKA Leeloo.
Will only work if you're Bruce Willis or a relative of Milla Jovovich.
////
As a programmer, the sequence of slashes immediately registers in my brain as a comment (even though slashes are normally used for line comments).
Of course, "////" is just a string; the number of slashes in the prefix and the suffix must be equal.
I tried the chosen answer, but found when I ran a shell script having it, the whole thing was getting printed to screen (similar to how jupyter notebooks print out everything in '''xx''' quotes) and there was an error message at end. It wasn't doing anything, but: scary. Then I realised while editing it that single-quotes can span multiple lines. So.. lets just assign the block to a variable.
x='
echo "these lines will all become comments."
echo "just make sure you don_t use single-quotes!"
ls -l
date
'
what's your opinion on this one?
function giveitauniquename()
{
so this is a comment
echo "there's no need to further escape apostrophes/etc if you are commenting your code this way"
the drawback is it will be stored in memory as a function as long as your script runs unless you explicitly unset it
only valid-ish bash allowed inside for instance these would not work without the "pound" signs:
1, for #((
2, this #wouldn't work either
function giveitadifferentuniquename()
{
echo nestable
}
}
Here's how I do multiline comments in bash.
This mechanism has two advantages that I appreciate. One is that comments can be nested. The other is that blocks can be enabled by simply commenting out the initiating line.
#!/bin/bash
# : <<'####.block.A'
echo "foo {" 1>&2
fn data1
echo "foo }" 1>&2
: <<'####.block.B'
fn data2 || exit
exit 1
####.block.B
echo "can't happen" 1>&2
####.block.A
In the example above the "B" block is commented out, but the parts of the "A" block that are not the "B" block are not commented out.
Running that example will produce this output:
foo {
./example: line 5: fn: command not found
foo }
can't happen
Simple solution, not much smart:
Temporarily block a part of a script:
if false; then
while you respect syntax a bit, please
do write here (almost) whatever you want.
but when you are
done # write
fi
A bit sophisticated version:
time_of_debug=false # Let's set this variable at the beginning of a script
if $time_of_debug; then # in a middle of the script
echo I keep this code aside until there is the time of debug!
fi
in plain bash
to comment out
a block of code
i do
:||{
block
of code
}

What is the purpose of the EOL semicolon in this bash script?

I'm trying to adapt some code I found for a monitor layout script.
...
while read l
do
dir=$(dirname $l);
status=$(cat $l);
dev=$(echo $dir | cut -d\- -f 2-);
if [ $(expr match $dev "HDMI") != "0" ]
...
As per the Bash man page: A semicolon can either be a metacharacter or a control operator.
I understand the metacharacter use is for consecutive commands on the same line. Is it being used as a control operator in this case? I haven't used it this way before and the script functions without it. I don't want to remove or keep it without understanding its purpose.
There might have been some thinking process going on along the lines of
"this should execute even if concatenated into a single line", or
"this was created from a single line originally".
But in that case you would need additional semicolons at the end of the while and if lines to make it work.
So, no, there is no purpose to them other than a bad habit by the programmer in question.
No, you will not need a semicolon in this case. You would need a semicolon in bash if there is more then one command on a single line. Here is an example of where a semicolon is often used in bash to make it more readable:
if [ $a -gt 12 ]; then
You see here, the if and the then are two different commands, but you can use the semicolon to put them on the same line, which makes the code easier to read in my opinion.
As per #anubhava's comment on the question"
No there is no apparent good reasons for this...

How does : <<'END' work in bash to create a multi-line comment block?

I found a great answer for how to comment in bash script (by #sunny256):
#!/bin/bash
echo before comment
: <<'END'
bla bla
blurfl
END
echo after comment
The ' and ' around the END delimiter are important, otherwise things inside the block like for example $(command) will be parsed and executed.
This may be ugly, but it works and I'm keen to know what it means. Can anybody explain it simply? I did already find an explanation for : that it is no-op or true. But it does not make sense to me to call no-op or true anyway....
I'm afraid this explanation is less "simple" and more "thorough", but here we go.
The goal of a comment is to be text that is not interpreted or executed as code.
Originally, the UNIX shell did not have a comment syntax per se. It did, however, have the null command : (once an actual binary program on disk, /bin/:), which ignores its arguments and does nothing but indicate successful execution to the calling shell. Effectively, it's a synonym for true that looks like punctuation instead of a word, so you could put a line like this in your script:
: This is a comment
It's not quite a traditional comment; it's still an actual command that the shell executes. But since the command doesn't do anything, surely it's close enough: mission accomplished! Right?
The problem is that the line is still treated as a command beyond simply being run as one. Most importantly, lexical analysis - parameter substitution, word splitting, and such - still takes place on those destined-to-be-ignored arguments. Such processing means you run the risk of a syntax error in a "comment" crashing your whole script:
: Now let's see what happens next
echo "Hello, world!"
#=> hello.sh: line 1: unexpected EOF while looking for matching `''
That problem led to the introduction of a genuine comment syntax: the now-familiar # (which was first introduced in the C shell created at BSD). Everything from # to the end of the line is completely ignored by the shell, so you can put anything you like there without worrying about syntactic validity:
# Now let's see what happens next
echo "Hello, world!"
#=> Hello, world!
And that's How The Shell Got Its Comment Syntax.
However, you were looking for a multi-line (block) comment, of the sort introduced by /* (and terminated by */) in C or Java. Unfortunately, the shell simply does not have such a syntax. The normal way to comment out a block of consecutive lines - and the one I recommend - is simply to put a # in front of each one. But that is admittedly not a particularly "multi-line" approach.
Since the shell supports multi-line string-literals, you could just use : with such a string as an argument:
: 'So
this is all
a "comment"
'
But that has all the same problems as single-line :. You could also use backslashes at the end of each line to build a long command line with multiple arguments instead of one long string, but that's even more annoying than putting a # at the front, and more fragile since trailing whitespace breaks the line-continuation.
The solution you found uses what is called a here-document. The syntax some-command <<whatever causes the following lines of text - from the line immediately after the command, up to but not including the next line containing only the text whatever - to be read and fed as standard input to some-command. Here's an alternate shell implementation of "Hello, world" which takes advantage of this feature:
cat <<EOF
Hello, world
EOF
If you replace cat with our old friend :, you'll find that it ignores not only its arguments but also its input: you can feed whatever you want to it, and it will still do nothing (and still indicate that it did that nothing successfully).
However, the contents of a here-document do undergo string processing. So just as with the single-line : comment, the here-document version runs the risk of syntax errors inside what is not meant to be executable code:
#!/bin/sh -e
: <<EOF
(This is a backtick: `)
EOF
echo 'In modern shells, $(...) is preferred over backticks.'
#=> ./demo.sh: line 2: bad substitution: no closing "`" in `
The solution, as seen in the code you found, is to quote the end-of-document "sentinel" (the EOF or END or whatever) on the line introducing the here document (e.g. <<'EOF'). Doing this causes the entire body of the here-document to be treated as literal text - no parameter expansion or other processing occurs. Instead, the text is fed to the command unchanged, just as if it were being read from a file. So, other than a line consisting of nothing but the sentinel, the here-document can contain any characters at all:
#!/bin/sh -e
: <<'EOF'
(This is a backtick: `)
EOF
echo 'In modern shells, $(...) is preferred over backticks.'
#=> In modern shells, $(...) is preferred over backticks.
(It is worth noting that the way you quote the sentinel doesn't matter - you can use <<'EOF', <<E"OF", or even <<EO\F; all have the same result. This is different from the way here-documents work in some other languages, such as Perl and Ruby, where the content is treated differently depending on the way the sentinel is quoted.)
Notwithstanding any of the above, I strongly recommend that you instead just put a # at the front of each line you want to comment out. Any decent code editor will make that operation easy - even plain old vi - and the benefit is that nobody reading your code will have to spend energy figuring out what's going on with something that is, after all, intended to be documentation for their benefit.
It is called a Here Document. It is a code block that lets you send a list of commands to another command or program
The string following the << is the marker determining the end of the block. If you send commands to no-op, nothing happens, which is why you can use it as a comment block.
That's heredoc syntax. It's a way of defining multi-line string literals.
As the answer at your link explains, the single quotes around the END disables interpolation, similar to the way single-quoted strings disable interpolation in regular bash strings.

bash spaces in whiptail/dialog menu items

I want to create a simple dialog with bash-dialog. I work with (X)DSL and bash-3.2. The latest (X)DSL is based on Linux 2.4.31 and comes with bash-2.05, however, bash-3.2 is downloadable from MyDSL/Testing. So, my script runs under '#!/bin/bash-3.2/bin/bash'.
The menu items the users can choose from come from a database.
Example database file 'armatures':
Indoor Lighting|Lighting for Indoor use
Outdoor Lighting|Lighting for Outdoor use
I retrieve the data into an array 'options' from the 'armatures' file with:
options=($(awk -F"|" '{ print $1,$2 }' armatures)
and in terminal 'echo' the array:
echo ${options[#]}
which shows:
"Indoor Armatures" "Lighting for Indoor use" "Outdoor Armatures" "Lighting for Outdoor use"
This looks OK to use as a selection menu with 'whiptail' but it isn't. The command line:
whiptail --clear --title "Armatures" --menu "Choose an armature" 50 80 10 ${options[#]}
shows:
column1-column2
Indoor-Armatures
Lighting-for
Indoor-use
Outdoor-Armatures
Lighting-for
Outdoor-use
in stead of:
column1-column2
Indoor armatures-Lighting for Indoor use
Outdoor armatures-Ligthing for Outdoor use
It seems that array elements with double quotes are ignored or not seen by 'whiptail'. I also tried "${options[#]}" but that always results on the first word 'Indoor'.
Aside from 'whiptail' I tried 'dialog' but they are the same: version information shows 'cdialog (ComeOn Dialog!) version 1.1-20080316' in both cases.
I have very limited resources and don't want to venture (yet) into 'xdialog', 'zenity', 'dzen' and the like, even if that would solve this. I am also limited to Linux 2.4.31 due to XDSL (for XBOX).
I've been browsing the Internet a lot but to no avail. What could be the solution with 'whiptail/dialog'?
The basic problem you are having comes from the order in which the shell parses command lines: it parses (and removes) quotes and escapes before it replaces variables with their values, and it never goes back and re-parses for any quotes or escapes within the replaced values. Consider this example:
var='"foo bar"' # Note that the single-quotes will be removed, and the
# double-quotes will be treated as part of the variable value.
somecmd $var # This runs cmd with 2 arguments: '"foo' and 'bar"'
In your case, I'm not sure where the double-quotes are coming from; they're not in the file listing you provided and the awk command won't add them. But in any case, you don't want them stored as part of the value, you want them around the variable reference:
var='foo bar' # Again, the single-quotes are removed.
somecmd "$var" # This runs cmd with a single argument: 'foo bar'
Your case is a little more complicated since you're using an array, but the same principle applies. Note that echoing a variable is highly misleading; if it shows what looks like proper quoting, that actually means there's something horribly wrong because it should show the arguments after quote removal.
So, how do you solve it? Try this:
options=()
while IFS="|" read col1 col2 || [ -n "$col1" ]; do
options+=("$col1" "$col2") # Note the double-quotes around the variable references
done <armatures
echo "options:"
printf " '$s'\n" "${options[#]}" # This prints each array element on a separate line
whiptail --clear --title "Armatures" --menu "Choose an armature" 50 80 10 "${options[#]}" # Again, double-quotes around the reference
UPDATE: I added a test ([ -n "$col1" ]) to execute the loop for an unterminated last line in the database file.
If the double-quotes are actually in the database file, you'll have to remove them; the easiest way to handle this probably to strip quotes while adding the strings to the array, using bash's ability to so string replacement (replacing '"' with blank) while building the array:
options=()
while IFS="|" read col1 col2 || [ -n "$col1" ]; do
options+=("${col1//'"'/}" "${col2//'"'/}")
done <armatures
The main problem is with the way bash (or any other shell) splits the command lines into "words", as mentioned in the first answer. The line splitting is done on the basis of the IFS - the Internal Field Separator - which is set to <space><tab><newline> by default. The clever chaps (Stephen Bourne etc) who developed the early shells obviously thought it was a good idea for users to be able to change it. It's also nice that it can be set to a multi-character value.
So all you really need to do is set the field separator to a newline, run the awk script, then set it back. You should also quote the array when you use it.
This should work for bash:
IFS=$'\n'
options=($(awk -F"|" '{ print $1,$2 }' armatures)
IFS=$' \t\n'
The array members are now properly defined. Then:
whiptail --clear --title "Armatures" --menu "Choose an armature" 50 80 10 "${options[#]}"
For more primitive shells like the Bourne Shell, you will probably need to set IFS by entering "IFS=" (without the quotes), opening single quotes, pressing <Enter>, then closing with a single quote. To reset it again, enter "IFS=" (without the quotes), open single quotes, enter a <space>, quote (e.g. Ctrl-V) a <tab>, hit <Enter>, then close with a single quote.

Resources