I'm completely new in the Linux world, i-m using a Bash shell and practicing a bit.
How can i perform a loop giving as counter an expression?
For example looping through the amount of word in a file.
for n in(wc -w text.txt)
do
echo "word number $n"
done
Unfortunately the above script its not working.
The only way i have found so far is to first print the value of wc -w text.txt, then assign it to a variable and then loop:
wc -w text.txt
a=10 //result saw by wc -w text.txt
for((b=0; b<a; b++))
do
echo "$b"
done
The problem is that i need to retrieve the value of wc -c and asssing it directly to a variable, in case i have to write a script that runs automatically, the problem is that
a= wc -w test.txt
Will not work,
Any advice?
A clean way to do this is
tmp=$(wc -w <text.txt)
for ((i=0; i<$tmp; i++))
do
echo $i;
done
The <text.txt part is necessary, because normally, the output of the wc command is
wordcount1 INPUTFILE1
wordcount2 INPUTFILE2
...
The max value for i must equal the word count of your file, so you must take away the INPUTFILE1 part before your loop works as intended. Using an input stream < rather than a filename takes care of this issue.
Using a temp variable prevents wc from being called on each iteration.
Related
This question already has answers here:
Bash variable scope
(7 answers)
Closed 8 years ago.
I have a very simple bash script to compute the sum of numbers appear at each line of a file (I know there are better ways for doing this but I actually need this sum as an auxiliary information and the script is supposed to something more later on). The script is as follows:
TOTAL=0;
cat $DATAFILE | while read LINE;
do
COUNT=`echo $LINE| awk '{print $2;}'`;
TOTAL=$((TOTAL+COUNT));
done
echo "Total = $TOTAL";
However, I always get the output "Total = 0". Surprisingly enough if I move the last line inside the while loop, I get the correct result. For example if the input file contains
A 5
B 3
C 6
I get the output
Total = 5
Total = 8
Total = 14
But the current version always outputs 0. It seems the value assigned to the variable TOTAL is somehow lost.
Can anyone help me out with the issue?
Thanks in advance
This is BashFAQ #24. Fortunately, you're only hitting it here because of an unnecessary use of cat -- you have no good reason to have a pipeline in use at all.
The right-hand side of a pipeline (like the rest of its contents, being made up of transient subshells) exits when the pipe is done unless you have shopt -s lastpipe active.
Instead, either add the line:
shopt -s lastpipe
or restructure your loop:
while read LINE;
do
COUNT=`echo $LINE| awk '{print $2;}'`;
TOTAL=$((TOTAL+COUNT));
done <"$DATAFILE"
...or, in a case where you really need to pipe from another process, use process substitution:
while read LINE;
do
COUNT=`echo $LINE| awk '{print $2;}'`;
TOTAL=$((TOTAL+COUNT));
done < <(cat "$DATAFILE")
By the way, if your shebang is #!/bin/bash, not #!/bin/sh, this would be better written as follows:
total=0
while read -r _ count do;
(( total += count ))
done <"$DATAFILE"
read can split lines on characters in IFS itself -- you don't need to use awk for that.
By convention, variable names should be all-lowercase unless they represent environment variables or shell builtins.
I have written a bash script, but I cannot understand what I am doing wrong. Here it is:
#!/bin/bash
VAR_LIST=('var_A' 'var_B' 'var_C')
FilePrefix='mystring_'
counter=1
for i in "${VAR_LIST[#]}"
do
grep "$i" > "${FilePrefix}${counter}.tab.txt"
counter=$((counter+1))
done <$1
Results: a file is created for each variable, but only the first one has data in it; the other files are empty. I have verified manually that each variable should return a few lines. The strange thing is that if I just do:
for i in "${VAR_LIST[#]}"
do
echo "$i"
done
I get the list of all the variables in VAR_LIST
You're only opening the input file once -- which means that once the first grep has read it, there's nothing left for future ones to read.
Since bash doesn't have a way to call seek(), you'll need to re-open the file once per invocation:
for i in "${VAR_LIST[#]}"
do
grep "$i" >"${FilePrefix}${counter}.tab.txt" <"$1"
counter=$((counter+1))
done
I'm writing a shell script that greps for $foo then counts the number of occurrences then runs a command. Each time that command is run, there is one less instance of $foo in that file. Uncertain on how to continuously read that file and reduce the value in the variable I set.
$count= `grep -o $foo /some/file |wc -w`
until [ $count -eq 0 ]
do
some_command_that_deletes_foo_in_file
done
However I realize that $count is set once at runtime and is not updated. What I want is $count to be updated to the current count in /some/file while the script is looping through /some/file until there is 0 instances of the phrase I'm grepping for. Uncertain to what the best approach is.
Unless you have additional code that you haven't showed us that depends on $count, you don't actually need to count occurrences; you just need to know whether the string appears in the file. For that, you can write:
while grep -q -- "$foo" /some/file ; do
some_command_that_deletes_foo_in_file
done
(using the fact that grep returns success when it finds the value, and failure when it does not, and using the -q flag to suppress its console output).
You could add the grep command inside the loop:
count=$(grep -o "$foo" /some/file |wc -w)
until (( count == 0 ))
do
some_command_that_deletes_foo_in_file
count=$(grep -o "$foo" /some/file |wc -w)
done
You simply want to delete the string "$foo"? Use sed:
sed "/$foo/d" /some/file > /some/other/file
The sed command is an editor. The /$foo/ is taking a regular expression (whatever the value of $foo), finding it in the file. The d tells it to delete the line.
sed doesn't usually do an in place edit. You usually have to write to another file and then to a move. However, some sed commands may have such a parameter. You can check your manage.
Second Try
I think it must take some action or perform some processing or something, and one of its effects is that one of the $foos is gone. (But I could be wrong.) – ruakh yesterday
This is what I get answering these questions at night.
You can take advantage of the fact that grep returns true (i.e. exit value = 0) if it can find your regular expression and false (i.e. exit value not equal to 0) otherwise:
while grep -o -q "$foo" "/some/file"
do
some_command_that_deletes_foo_in_file
done
The -q tells grep not to output anything. Instead, the exit value of grep will be true if the string is found and false if it isn't.
Read your manpage on grep. Some grep commands don't have the -q parameter. In that case, you'l need to redirect both STDOUT and STDERR to /dev/null.
Another tact may be to do your count of the number of lines, and then use that as a counter:
count=$(grep -o "$foo" "/some/file" | wc -w) # $(...) is the preferred syntax over `...`
for loop in {1..$count}
do
some_command_that_deletes_foo_in_file
done
The advantage is that you only grep through the file once (which maybe a long operation). However, your count maybe incorrect if $foo is on more than one line.
A few notes:
The $(...) is preferred over backticks
When you set a variable, you don't use the dollar sign in front of that variable. Note I have count= and not $count=.
Watch spaces. count= $(...) is wrong because there's a space after the equals sign.
There are 2 pieces of code here, and the value in $1 is the name of a file which contains 3 lines of text.
Now, I have a problem. In the first piece of the code, I can't get the "right" value out of the loop, but in the second piece of the code, I can get the right result. I don't know why.
How can I make the first piece of the code get the right result?
#!/bin/bash
count=0
cat "$1" | while read line
do
count=$[ $count + 1 ]
done
echo "$count line(s) in all."
#-----------------------------------------
count2=0
for var in a b c
do
count2=$[ $count2 + 1 ]
done
echo "$count2 line(s) in all."
This happens because of the pipe before the while loop. It creates a sub-shell, and thus the changes in the variables are not passed to the main script. To overcome this, use process substitution instead:
while read -r line
do
# do some stuff
done < <( some commad)
In version 4.2 or later, you can also set the lastpipe option, and the last command
in the pipeline will run in the current shell, not a subshell.
shopt -s lastpipe
some command | while read -r line; do
# do some stuff
done
In this case, since you are just using the contents of the file, you can use input redirection:
while read -r line
do
# do some stuff
done < "$file"
How can I write a shell script file_readable which:
accepts some number of names as arguments,
checks each name to see if it is a regular file and readable, and
outputs a count of the number of such files.
For example:
$ sh file_readable /etc/fstab /etc/ssh/ssh_host_rsa_key /etc/does-not-exist
1
Of these, only /etc/fstab is likely to both exist and be readable.
So far I have put this together but it does not work correctly - can anybody help me please ?:
#!/bin/sh
for filename in "$#"
do
if test -f "$filename"
then echo | wc -l
else echo $?
fi
done
then echo | wc -l
If file exists and is a regular you print number of lines in empty string plus "\n", which is equal 1 always. Sound not quite usable, isn't it?
All you need is incrementing some counter and printing it in the end.
#!/bin/sh
readable_files=0
for filename in "$#"; do
if test -f "$filename"; then
readable_files=$(( readable_files + 1 ))
fi
done
echo "${readable_files}"