Copy number of line composed by special character in bash - bash

I have an exercise where I have a file and at the begin of it I have something like
#!usr/bin/bash
# tototata
#tititutu
#ttta
Hello world
Hi
Test test
#zabdazj
#this is it
And I have to take each first line starting with a # until the line where I don't have one and stock it in a variable. In case of a shebang, it has to skip it and if there's blank space between lines, it has to skip them too. We just want the comment between the shebang and the next character.
I'm new to bash and I would like to know if there's a way to do it please ?
Expected output:
# tototata
#tititutu
#ttta

Try in this easy way to better understand.
#!/bin/bash
sed 1d your_input_file | while read line;
do
check=$( echo $line | grep ^"[#;]" )
if ([ ! -z "$check" ] || [ -z "$line" ])
then
echo $line;
else
exit 1;
fi
done

This may be more correct, although your question was unclear about weather the input file had a script shebang, if the shebang had to be skipped to match your sample output, or if the input file shebang was just bogus.
It is also unclear for what to do, if the first lines of the input file are not starting with #.
You should really post your assignment's text as a reference.
Anyway here is a script that does collects first set of consecutive lines starting with a sharp # into the arr array variable.
It may not be an exact solution to your assignment (witch you should be able to solve with what your previous lessons taught you), but will get you some clues and keys to iterate reading lines from a file and testing that lines starts with a #.
#!/usr/bin/env bash
# Our variable to store parsed lines
# Is an array of strings with an entry per line
declare -a arr=()
# Iterate reading lines from the file
# while it matches Regex: ^[#]
# mean while lines starts with a sharp #
while IFS=$'\n' read -r line && [[ "$line" =~ ^[#] ]]; do
# Add line to the arr array variable
arr+=("$line")
done <a.txt
# Print each array entries with a newline
printf '%s\n' "${arr[#]}"

How about this (not tested, so you may have to debug it a bit, but my comments in the code should explain what is going on):
while read line
do
# initial is 1 one the first line, and 0 after this. When the script starts,
# the variable is undefined.
: ${initial:=1}
# Test for lines starting with #. Need to quote the hash
# so that it is not taken as comment.
if [[ $line == '#'* ]]
then
# Test for initial #!
if (( initial == 1 )) && [[ $line == '#!'* ]]
then
: # ignore it
else
echo $line # or do whatever you want to do with it
fi
fi
# stop on non-blank, non-comment line
if [[ $line != *[^\ ]* ]]
then
break
fi
initial=0 # Next line won't be an initial line
done < your_file

Related

Get first uncommented line (i.e not staring with #) in bash script

I am processing a developer commit message in git hook.
let's say the file has the following content
\n new lines here
# this is a sample commit
# only for the developers
Ticket-ID: we fix old bugs and introduces new ones
we always do this stuff
so cool, not really :P
# company name
My intention is to get only this line Ticket-ID : we fix old bugs and introduces new ones
User123's comment is nice and terse: grep -E "^[[:alnum:]]" file |head -n 1 however is does not catch lines of text that start with non-alphanumeric characters that are not a # such as commit messages that start with an emoji, dashes, parenthesis, etc..
๐Ÿš€ yeah this line is an exception
--> This is also an edge case
(so is this)
To catch all edge cases you can loop through the file and check each $line with a negated ! regexp operator =~ for:
Not being a newline ! $line =~ (^[^\n ]*$)
Not starting with a pound sign ! $line =~ ^#
Not being a line consisting of all spaces ! $line =~ (^[ ]*$)
Then just echo the $line and break if those conditions are met:
# file parse.sh
#!/bin/bash
if [[ -f $1 ]]; then
while IFS= read -r line
do
[[ ! $line =~ (^[^\n ]*$) && ! $line =~ ^# && ! $line =~ (^[ ]*$) ]] && echo "$line" && break
done < "$1"
fi
# file commit .txt
# this is a sample commit
# only for the developers
Ticket-ID: we fix old bugs and introduces new ones
we always do this stuff
so cool, not really :P
# company name
Now you can invoke the parse.sh like this
bash parse.sh commit.txt
Or save the results to a variable using a subshell
result=$(bash parse.sh commit.txt); echo "$result"
Below single line grep should work as per your requirement:
grep -E "^[[:alnum:]]" file |head -n 1
Explanation:
^[[:alnum:]] :: to capture only the line starting with any alphanumeric character[0-9A-Za-z]
head -n 1 :: to capture the first occurrence

Not able to skip blank lines in a shell script

I am reading a text file line by line and taking the count of all lines as a part of my requirement.
When there is blank line then it get messed up. I tried with if condition for [ -z "$line" ] , however not able to succeed.
Here is my current code:
countNumberOfCases() {
echo "2. Counting number of test cases -----------"
cd $SCRIPT_EXECUTION_DIR
FILE_NAME=Features
while read line || [[ -n "$line" ]]
do
TEST_CASE="$line"
if [ "${TEST_CASE:0:1}" != "#" ] ; then
cd $MVN_EXECUTION_DIR
runTestCase
fi
done < $FILE_NAME
echo " v_ToalNoOfCases : = " $v_ToalNoOfCases
}
And below is Features file
web/sprintTwo/TC_002_MultipleLoginScenario.feature
#web/sprintOne/TC_001_SendMoneyTransaction_Spec.feature
web/sprintTwo/TC_003_MultipleLoginScenario.feature
#web/sprintOne/TC_004_SendMoneyTransaction_Spec.feature
When there is blank line it wont work properly so my requirement is that if there is blank line then it should be skipped and should not get considered.
You can write your loop in a little more robust way:
#!/bin/bash
while read -r line || [[ $line ]]; do # read lines one by one
cd "$mvn_execution_dir" # make sure this is an absolute path
# or move it outside the loop unless "runTestCase" function changes the current directory
runTestCase "$line" # need to pass the argument?
done < <(sed -E '/^[[:blank:]]*$/d; /^[[:blank:]]+#/d' "$file_name") # strip blanks and comments
A few things:
get your script checked at shellcheck for common mistakes
see this post for proper variable naming convention:
Correct Bash and shell script variable capitalization
see this discussion about [ vs [[ in Bash
Test for non-zero length string in Bash: [ -n โ€œ$varโ€ ] or [ โ€œ$varโ€ ]
about reading lines from a text file
Looping through the content of a file in Bash

How to check if the matched EMPTY line is the LAST line of a file in a while IFS read

I have a while IFS read loop to check for different matches in the lines.
I check for and empty/blank line like this:
while IFS= read -r line; do
[[ -z $line ]] && printf some stuff
I also want to check if the matched empty/blank is also the last line of the file. I am going to run this script on a lot of files, they all:
-end with an empty line
-they are all a DIFFERENT LENGTH so I cannot assume anything
-they have other empty lines but not necessarily at the very end (this is why I have to differentiate)
Thanks in advance.
As chepner has noted, in a shell line-reading loop the only way to know whether a given line is the last one is to try to read the next one.
You can emulate "peeking" at the next line using the code below, which allows you to detect the desired condition while still processing the lines uniformly.
This solution may not be for everyone, because the logic is nontrivial and therefore requires quite a bit of extra, non-obvious code, and processing is slowed down as well.
Note that the code assumes that the last line has a trailing \n (as all well-formed multiline text input should have).
#!/usr/bin/env bash
eof=0 peekedChar= hadEmptyLine=0 lastLine=0
while IFS= read -r line || { eof=1; (( hadEmptyLine )); }; do
# Construct the 1-2 element array of lines to process in this iteration:
# - an empty line detected in the previous iteration by peeking, if applicable
(( hadEmptyLine )) && aLines=( '' ) || aLines=()
# - the line read in this iteration, with the peeked char. prepended
if (( eof )); then
# No further line could be read in this iteration; we're here only because
# $hadEmptyLine was set, which implies that the empty line we're processing
# is by definition the last one.
lastLine=1 hadEmptyLine=0
else
# Add the just-read line, with the peeked char. prepended.
aLines+=( "${peekedChar}${line}" )
# "Peek" at the next line by reading 1 character, which
# we'll have to prepend to the *next* iteration's line, if applicable.
# Being unable to read tells us that this is the last line.
IFS= read -n 1 peekedChar || lastLine=1
# If the next line happens to be empty, our "peeking" has fully consumed it,
# so we must tell the next iteration to insert processing of this empty line.
hadEmptyLine=$(( ! lastLine && ${#peekedChar} == 0 ))
fi
# Process the 1-2 lines.
ndx=0 maxNdx=$(( ${#aLines[#]} - 1 ))
for line in "${aLines[#]}"; do
if [[ -z $line ]]; then # an empty line
# Determine if this empty line is the last one overall.
thisEmptyLineIsLastLine=$(( lastLine && ndx == maxNdx ))
echo "empty line; last? $thisEmptyLineIsLastLine"
else
echo "nonempty line: [$line]"
fi
((++ndx))
done
done < file

Creating new shell variables in a loop

Through some here help I was to get only the text between quotation marks in a file like so:
text undefined but text
something something text something
shouldfindthis "dolphin"
butalsothis "elephant"
by doing:
i=0
regex='(".*?")' # Match all what is between "
while read line # read file line by line
do
if [[ $line =~ $regex ]]; then # If regex match
vars[$i]="${BASH_REMATCH[1]}" # store capturing group in a array
i=$((i + 1))
fi
done < txt # file "txt"
# Print what we found
if [ -v "vars" ]; then # if we found something , "vars" will exist
for var in "${vars[#]}"
do
echo "$var"
done
fi
Yielding an output:
$ ./script.sh
"dolphin"
"elephant"
Is there a way to mark these as variables? As such that dolphin is $text1 and elephant is $text2? in doing so that I can replace only the text within the paranthasis?
First: Using arrays, as your code already does, is the best-practice approach. Don't change it. However, if you really want to write to separate variables rather than array, then replace:
vars[$i]="${BASH_REMATCH[1]}" # this could also be vars+=( "${BASH_REMATCH[1]}" )
...with...
printf -v "text$i" '%s' "${BASH_REMATCH[1]}"
If you want to eliminate the quotes themselves, move them outside the grouping operator in your regex:
regex='"([^"]*)"'

bash: read line and keep spaces

I am trying to read lines from a file containing multiple lines. I want to identify lines that contain only spaces.
By definition, an empty line is empty and does not contain anything (including spaces).
I want to detect lines that seems to be empty but they are not (lines that contain spaces only)
while read line; do
if [[ `echo "$line" | wc -w` == 0 && `echo "$line" | wc -c` > 1 ]];
then
echo "Fake empty line detected"
fi
done < "$1"
But because read ignores spaces in the start and in the end of a string my code isn't working.
an example of a file
hi
hi
(empty line, no spaces or any other char)
hi
(two spaces)
hey
Please help me to fix the code
Disable word splitting by clearing the value of IFS (the internal field separator):
while IFS= read -r line; do
....
done < "$1"
The -r isn't strictly necessary, but it is good practice.
Also, a simpler way to check the value of line (I assume you're looking for a line with nothing but whitespace):
if [[ $line =~ ^$ ]]; then
echo "Fake empty line detected"
fi
Following your code, it can be improved.
while read line; do
if [ -z "$line" ]
then
echo "Fake empty line detected"
fi
done < "$1"
The test -z checks if $line is empty.
Output:
Fake empty line detected
Fake empty line detected

Resources