Can anyone suggest how to comment particular lines in the shell script other than #?
Suppose I want to comment five lines. Instead of adding # to each line, is there any other way to comment the five lines?
You can comment section of a script using a conditional.
For example, the following script:
DEBUG=false
if ${DEBUG}; then
echo 1
echo 2
echo 3
echo 4
echo 5
fi
echo 6
echo 7
would output:
6
7
In order to uncomment the section of the code, you simply need to comment the variable:
#DEBUG=false
(Doing so would print the numbers 1 through 7.)
Yes (although it's a nasty hack). You can use a heredoc thus:
#!/bin/sh
# do valuable stuff here
touch /tmp/a
# now comment out all the stuff below up to the EOF
echo <<EOF
...
...
...
EOF
What's this doing ? A heredoc feeds all the following input up to the terminator (in this case, EOF) into the nominated command. So you can surround the code you wish to comment out with
echo <<EOF
...
EOF
and it'll take all the code contained between the two EOFs and feed them to echo (echo doesn't read from stdin so it all gets thrown away).
Note that with the above you can put anything in the heredoc. It doesn't have to be valid shell code (i.e. it doesn't have to parse properly).
This is very nasty, and I offer it only as a point of interest. You can't do the equivalent of C's /* ... */
for single line comment add # at starting of a line
for multiple line comments add ' (single quote) from where you want to start & add ' (again single quote) at the point where you want to end the comment line.
You have to rely on '#' but to make the task easier in vi you can perform the following (press escape first):
:10,20 s/^/#
with 10 and 20 being the start and end line numbers of the lines you want to comment out
and to undo when you are complete:
:10,20 s/^#//
Related
What I'm attempting to do is receive values from the command line (instead of using the read method and asking the user to enter the values and/or file names in multiple steps).
./hello.sh 5 15 <file_name.txt
I have heard that simply using an array can help do the same, but I am not able to-
Avoid printing
5 15
on the next line
Since 5 and 15 are being printed, I'd expect the string 'abcdefgh' (contents of file_name.txt) to be printed; however, the output stops at
5 15
I would really appreciate it if someone could point out why my code isn't sufficient, and if possible, point me in the direction of some learning resources to broaden my knowledge of this concept.
Here is the code:
#! /usr/bin/bash
echo "$#"
I am simply testing things out (wanted to print out the variables before doing anything with and to them).
<file_name.txt is a redirection. It is not passed as a parameter. The parameters of the script are 5 and 15. The < redirects the file file_name.txt to standard input stdin of the script. You can read from stdin with for example cat.
#!/usr/bin/bash
echo "$#" # outputs parameters of the script joined with spaces
cat # redirects standard input to standard output, i.e. reads from the fiel
why my code isn't sufficient
Your script is not reading from the file, so the content of the file is ignored.
point me in the direction of some learning resources
File descriptors and redirections and standard streams are basic tools in shell - you should learn about them in any shell and linux introduction. My 5 min google search resulted in this link https://www.digitalocean.com/community/tutorials/an-introduction-to-linux-i-o-redirection , which looks like some introduction to the topic.
Will this work?
./hello.sh 5 15 `catfile_name.txt`
And update hello.sh to:
#! /usr/bin/bash
shift 2
echo $#
Here is a more generic solution. It looks at each input parameter in turn. If it is a valid file, it outputs the contents of the file. Otherwise if just prints the parameter.
#! /usr/bin/bash
for $parameter in "${#}"; do # Quotation marks avoid splitting parameters with spaces.
if [ -f $parameter ]; then # '-f {value}' tests if {value} is a file.
cat $parameter
else
echo $parameter # You could also use 'echo -n ...' to skip newlines.
fi
done
I intend to read the lines of a short .txt file and assign each line to variables containing the line number in the variable name.
File example.txt looks like this:
Line A
Line B
When I run the following code:
i=1
while read line; do
eval line$i="$line"
echo $line
((i=i+1))
done < example.txt
What I would expect during execution is:
Line A
Line B
and afterwards being able to call
$ echo $line1
Line A
$ echo $line2
Line B
However, the code above results in the error:
-bash: A: command not found
Any ideas for a fix?
Quote-removal happens twice with eval. Your double-quotes are getting removed before eval even runs. I'm not even going to directly answer that part, because there are better ways to do this:
readarray line < example.txt # bash 4
echo "${line[0]}"
Or, to do exactly what you were doing, with a different variable for each line:
i=1
while read line$((i++)); do
:
done < example.txt
Also check out printf -v varname "%s" value for a better / safer way to assign by reference.
Check out the bash-completion code if you want to see some complicated call-by-reference bash shenanigans.
Addressing your comment: if you want to process lines as they come in, but still save previous lines, I'd go with this construct:
lines=()
while read l;do
lines+=( "$l" )
echo "my line is $l"
done < "$infile"
This way you don't have to jump through any syntactic hoops to access the current line (vs. having to declare a reference-variable to line$i, or something.)
Bash arrays are really handy, because you can access a single element by value, or you can do "${#lines[#]}" to get the line count. Beware that unset lines[4] leaves a gap, rather than renumbering lines[5:infinity]. See the "arrays" section in the bash man page. To find the part of the manual that documents $# expansion, and other stuff, search in the manual for ##. The Parameter Expansion section is the first hit for that in the bash 4.3 man page.
eval line$i="$line" tells bash to evaluate the string "line1=Line A", which attempts to invoke a command named A with the environment variable "line1" set to the value of Line. You probably want to do eval "line$i='$line'"
This question already has answers here:
Empty Body For Loop Linux Shell
(4 answers)
Closed 6 months ago.
I am trying to learn shell scripting, so I created a simple script with a loop that does nothing:
#!/bin/bash
names=(test test2 test3 test4)
for name in ${names[#]}
do
#do something
done
however, when I run this script I get the following errors:
./test.sh: line 6: syntax error near unexpected token done'
./test.sh: line 6: done'
What have I missed here? are shell scripts 'tab sensitive'?
No, shell scripts are not tab sensitive (unless you do something really crazy, which you are not doing in this example).
You can't have an empty while do done block, (comments don't count)
Try substituting echo $name instead
#!/bin/bash
names=(test test2 test3 test4)
for name in ${names[#]}
do
printf "%s " $name
done
printf "\n"
output
test test2 test3 test4
dash and bash are a bit brain-dead in this case, they do not allow an empty loop so you need to add a no op command to make this run, e.g. true or :. My tests suggest the : is a bit faster, although they should be the same, not sure why:
time (i=100000; while ((i--)); do :; done)
n average takes 0.262 seconds, while:
time (i=100000; while ((i--)); do true; done)
takes 0.293 seconds. Interestingly:
time (i=100000; while ((i--)); do builtin true; done)
takes 0.356 seconds.
All measurements are an average of 30 runs.
Bash has a built-in no-op, the colon (:), which is more lightweight
than spawning another process to run true.
#!/bin/bash
names=(test test2 test3 test4)
for name in "${names[#]}"
do
:
done
EDIT: William correctly points out that true is also a shell built-in, so take this answer as just another option FYI, not a better solution than using true.
You could replace the nothing with 'true' instead.
You need to have something in your loop otherwise bash complains.
This error is expected with some versions of bash where the script was edited on Windows and so the script actually looks as follows:
#!/bin/bash^M
names=(test test2 test3 test4)^M
for name in ${names[#]}^M
do^M
printf "%s " $name^M
done^M
printf "\n"^M
where the ^M represents the carriage-return character (0x0D). This can easily be seen in vi by using the binary option as in:
vi -b script.sh
To remove those carriage-return characters simply use the vi command:
1,$s/^M//
(note that the ^M above is a single carriage-return character, to enter it in the editor use sequence Control-V Control-M)
Is it possible to make changes to a line written to STDOUT in shell, similar to the way many programs such as scp do?
The point would be to allow me to essentially have a ticker, or a monitor of some sort, without it scrolling all over the screen.
You can manipulate the terminal with control characters and ANSI escape codes. For example \b returns the cursor one position back, and \r returns it to the beginning of the line. This can be used to make a simple ticker:
for i in $(seq 10)
do
echo -en "Progress... $i\r" # -e is needed to interpret escape codes
sleep 1
done
echo -e "\nDone."
With ANSI escape codes you can do even more, like clear part of the screen, jump to any position you want, and change the output color.
You can overwrite the last printed line by printing the \r character.
For instance this:
for i in `seq 1 10`; do
echo -n $i;
sleep 1;
echo -n -e "\r" ;
done
Will print 1 then update it with 2 and so on until 10.
You can do modify the output of stdout using another program in a pipeline. When you run the program you use | to pipe the input into the next program. The next program can do whatever it wants with the output. A general purpose program for modifying the output of a program is sed, or you could write something yourself that modifies the data from the previous program.
A shell program would be something like:
while read line; do
# do something with $line and output the results
done
so you can just:
the_original_program | the_above_program
I am trying to figure out a way to display the longest line entered from user inputs.
For example, my script so far will have the user input in 4 lines:
Hello
Hello will
Hello will turner
Hello will turner honey
In my code I have:
echo "Please enter 4 lines:"
read LINE1
read LINE2
read LINE3
read LINE4
I am wondering if there is a way for me to count each of my lines and then output the biggest one. Making a file would probably be easiest but I wanted to know if I could just use the Bash commands to do so.
If you are only interested in the longest line then you can use a loop and compare the current line with the next line you read:
#!/bin/bash
max=0
for((i=0;i<4;i++)); do
read -r line
len=${#line}
if [[ len -gt max ]] ; then
max=$len
long="${line}"
fi
done
echo longest line="${long}" length="${max}"
If you want to keep the other lines, then you can use an array and apply the same logic on the array.
Assumptions
You don't explain what you expect to happen when two or more lines are the same length. I therefore make the assumption that when two lines are the same length, one should store and report the newer line as "longest."
Compare Line Lengths with Bash Expansions
A more idiomatic (and in my opinion clearer) way of doing this with Bash 4.x would be:
#!/usr/bin/env bash
# Guard against exported environment variables.
unset longest_line
for line in {1..4}; do
read -p "Enter line $line: "
(( "${#REPLY}" >= "${#longest_line}" )) && longest_line="$REPLY"
done
echo "$longest_line"
This uses a variety of shell expansions, the read builtin's -p flag to prompt, read's default REPLY variable to hold the result, and a line-length comparison against the longest line seen so far to perform the key task.
In this example, you don't even have to initialize a value for longest_line since the length of an unset variable is zero, but it's a good defensive programming practice not to rely on the variable being unset. If you prefer to actually set the initial state of the variable yourself, you can set it to the empty string instead with longest_line=''.
The code above will generate the expected result:
$ bash longest_line.sh
Enter line 1: foo
Enter line 2: bar
Enter line 3: foo bar
Enter line 4: baz
foo bar