Grep command in array - bash

For a homework assignment I have to Take the results from the grep command, and write out up to the first 5 of them, numbering them from 1 to 5. (Print the number, then a space, then the line from grep.) If there are no lines, print a message saying so. So far I managed to store the grep command in an array but this is where I've gotten stuck: Can anyone provide guidance as to how to proceed in printing this as stated above
pattern="*.c"
fileList=$(grep -l "main" $pattern)
IFS=$"\n"
declare -a array
array=$fileList
for x in "${array[#]}"; do
echo "$x"
done

you can grep options -c and -l
pattern="*.c"
searchPattern="main"
counter=1
while read -r line ; do
IFS=':' read -r -a lineInfo <<< "$line"
if [[ $counter > 5 ]]; then
exit 1
fi
if [[ ${lineInfo[1]} > 0 ]]; then
numsOfLine=""
while read -r fileline ; do
IFS=':' read -r -a fileLineInfo <<< "$fileline"
numsOfLine="$numsOfLine ${fileLineInfo[0]} "
done < <(grep -n $searchPattern ${lineInfo[0]})
echo "$counter ${lineInfo[0]} match on lines: $numsOfLine"
let "counter += 1"
else
echo "${lineInfo[0]} no match lines"
fi
done < <(grep -c $searchPattern $pattern)

If you're only allowed to use grep and bash(?):
pattern="*.c"
fileList=($(grep -l "main" $pattern))
if test ${#fileList[#]} = 0 ; then
echo "No results"
else
n=0
while test $n -lt ${#fileList[#]} -a $n -lt 5 ; do
i=$n
n=$(( n + 1 ))
echo "$n ${fileList[$i]}"
done
fi
If you are allowed to use commands in addition to grep, you can pipe the results through nl to add line numbers, then head to limit the results to the first 5 lines, then a second grep to test if there were any lines. For example:
if ! grep -l "main" $pattern | \
nl -s ' ' | sed -e 's/^ *//' | \
head -n 5 | grep '' ; then
echo "No results"
fi

Related

How to compare the 3rd octet of an IP to the 3rd octet in an array of IP in bash

Probably there is an easier way to do it but I have tried below and could not get it to work
ip=$1 #got the IP input 10.100.251.2
#broken into 4 octets via read
IFS="." read -r octet1 octet2 octet3 octet4 <<<"${ip}"
#the list to compare against
cat tmp_brd
10.100.123.255
10.100.127.255
10.100.239.255
10.100.255.255
#the list for the ouput which corresponds line by line to tmp_brd
cat tmp_sm
10.100.120.0
10.100.124.0
10.100.224.0
10.100.240.0
#the tmp_brd and tmp_sm arrays are collected from grep/awk the same file and each elements have 1 to 1 relation
mapfile -t sm_array <tmp_sm
mapfile -t brd_array <tmp_brd
brd_ln=${#brd_array[#]}
for ((i = 0; i < ${brd_ln}; i++)); do
IFS="." read -r octet1$i octet2$i octet3$i octet4$i <<<"${brd_array[$i]}"
if [[ $octet3 -lt $octet3$i ]]; then
echo "${sm_array[$i]}" >>subnet
fi
done
so basically ip=10.100.251.2 will match subnet=10.100.240.0 (from tmp_sm)
Solved it!
Thanks
mapfile -t sm_array <tmp_sm
mapfile -t brd_array <tmp_brd
brd_ln=${#brd_array[#]}
for ((i = 0; i < ${brd_ln}; i++)); do
IFS="." read -r oct1 oct2 oct3 oct4 <<<"${brd_array[$i]}"
if [[ $octet3 -lt $oct3 ]]; then
subnet=${sm_array[$i]}
break
fi
done
I think you would be better off using ipcalc for this:
#!/bin/bash
mapfile -t network <tmp_sm
mapfile -t broadcast <tmp_brd
let l=${#network[#]}-1
declare -a networks
declare -a masks
for i in $(seq 0 $l)
do
networks[$i]=$(ipcalc ${network[$i]} - ${broadcast[$i]} | tail -1)
masks[$i]=$(echo ${networks[$i]} | cut -d/ -f2)
done
ip=10.100.251.2 # just hardcoding one for test
for i in $(seq 0 $l)
do
echo "checking $ip against ${network[$i]} - ${broadcast[$i]} (${masks[$i]} bit mask)"
n=$(ipcalc ${ip}/${masks[$i]} | grep ^Network | awk '{print $2}' )
if [[ ${networks[$i]} == $n ]]
then
echo "$ip is in network ${networks[$i]}"
ipcalc $ip/${masks[$i]}
break;
fi
done

bash: syntax error in while loop

so in a bash script, have a while loop that read lines from the outpt of some pippelined sort commands. i get an error: line 13: syntax error near unexpected token 'done'
line 13 is the last line, where the "done" and the pipeline is. my script:
#!/bin/bash
while read -a line; do
if [[ ${line[2]} < $1 ]]; then
continue
fi
if [[ $2 != -MM ]]; then
if [[ ${line[3]} = N ]]; then
continue
fi
fi
echo -n "${line[0]} "
echo ${line[1]}
done < <(sort -b rooms | sort -sk2 | sort -sk4 | sort -sk3)
tnx.
I would first try to write the date to temp so I can see what the sorting is doing by inspecting the temp file. Then read that in line by line.
#!/usr/bin/env bash
TMPF="/tmp/tmp-file.dat"
sort -b rooms | sort -sk2 | sort -sk4 | sort -sk3 > "${TMPF}"
while IFS= read -r line ;do
if [[ ${line[2]} -ge $1 ]] && [[ $2 != -MM ]] && [[ ${line[3]} = N ]]; then
echo -n "${line[0]} "
echo ${line[1]}
fi
done < "${TMPF}"
You can use without using ()
like done < rooms |sort -b | sort -sk2 | sort -sk4 | sort -sk3

How can I assign expressions to a variable for read line?

I have a bash while read line block reading from a text file specified by $filename:
IFS=''
while read -r line
do
...
done < $filename
Instead of reading the whole file each time, I would like to supply different inputs in the redirect depending on the arguments supplied to the script.
Whole file: done < "$filename"
start at line x: done < <(tail -n +"$x" "$filename")
line x to line y: done < <(tail -n +"$x" "$filename" | head -n "$y")
start to line y: done < <(head -n "$y" "$filename")
How can I assign these inputs to a variable ahead of time to be read by the while loop?
My input file is ~4GB with some 58M lines (all with different lengths), and may grow or shrink from time to time. Reading https://unix.stackexchange.com/questions/47407/cat-line-x-to-line-y-on-a-huge-file it appears that tail | head is the fastest method to read from the middle of a file, so given the file size, I'm deliberately avoiding awk and sed for the most part.
Your data is too big to read in whole. The good news is that the contents of a process substitution is a shell script, so you can write:
while IFS= read -r line; do
...
done < <(
if [[ $x && $y ]]; then tail -n +"$x" "$filename" | head -n "$y"
elif [[ $x ]]; then tail -n +"$x" "$filename"
elif [[ $y ]]; then head -n "$y" "$filename"
else cat "$filename"
fi
)
One thing I don't like about process substitutions is that code follows the loop for which it is input. It would be nice if it appeared first. I think this will work, but is untested:
# set up file descriptor 3
exec 3< <(
if [[ $x && $y ]]; then tail -n +"$x" "$filename" | head -n "$y"
elif [[ $x ]]; then tail -n +"$x" "$filename"
elif [[ $y ]]; then head -n "$y" "$filename"
else cat "$filename"
fi
)
# iterate over lines read from fd 3
while IFS= read -u3 -r line; do
...
done
# close fd 3
exec 3<&-
I might handle all of these as part of the loop condition, with an explicitly maintained line counter.
start=10
end=30
i=0
while ((i <= end )) && IFS= read -r line; do
(( i++ >= start )) || continue
...
done < "$filename"
However, if you might skip a significant number of lines at the beginning, it might be more efficient to use sed
while IFS= read -r line; do
...
done < <(sed -n "$start,$stop p" "$filename")
or awk:
while IFS= read -r line; do
...
done < <(awk -v start "$start" -v end "$end" 'NR >= start && NR <= end' "$filename")
This then raises the question of how much of the body of the while loop can be moved into awk itself.

How to remove this repeated call to `ack`

I'm new to bash and I am writing a shell script that goes through the listed dependencies in package.json (jq), sees how many times it's used (ack), and if it's less than 2 times, echo that.
arr=( $(jq -r '.dependencies | keys | .[]' package.json) )
for i in "${arr[#]}"
do
n=$(ack $i --ignore-dir=dist --ignore-file='match:/checkDependencies.sh|package.json/' | wc -l)
if [[ $n -le 2 ]]; then
echo "Package $i has too few occurences"
ack $i --ignore-dir=dist --ignore-file='match:/checkDependencies.sh|package.json/'
echo
fi
done
You can see that I ack twice. How can I just ack once? I tried setting the output to a variable but it's not working how I want it to.
output from john1024's answer:
bash -x checkDependencies.sh
+ arr=($(jq -r '.devDependencies | keys | .[]' package.json))
++ jq -r '.devDependencies | keys | .[]' package.json
checkDependencies.sh: line 4: syntax error near unexpected token `s=$(ack "$i" --ignore-dir=dist --ignore-file='match:/package.json/')'
checkDependencies.sh: line 4: ` s=$(ack "$i" --ignore-dir=dist --ignore-file='match:/package.json/')'
provided solution (now with the do added)
arr=( $(jq -r '.devDependencies | keys | .[]' package.json) )
for i in "${arr[#]}"
do
s=$(ack "$i" --ignore-dir=dist --ignore-file='match:/package.json/')
if [ "$(wc -l <<<"$s")" -le 2 ]; then
echo "Package $i has too few occurences"
echo "$s"
fi
done
To use ack only once, try:
arr=( $(jq -r '.dependencies | keys | .[]' package.json) )
for i in "${arr[#]}"
do
s=$(ack "$i" --ignore-dir=dist --ignore-file='match:/checkDependencies.sh|package.json/')
if [ "$(wc -l <<<"$s")" -le 2 ]; then
echo "Package $i has too few occurences"
echo "$s"
fi
done
Here, for each package, we store ack's output in variable s and then use s wherever the output of ack is needed.

count words in a file without using wc

Working in a shell script here, trying to count the number of words/characters/lines in a file without using the wc command. I can get the file broken into lines and count those easy enough, but I'm struggling here to get the words and the characters.
#define word_count function
count_stuff(){
c=0
w=0
l=0
local f="$1"
while read Line
do
l=`expr $line + 1`
# now that I have a line I want to break it into words and characters???
done < "$f"
echo "Number characters: $chars"
echo "Number words: $words"
echo "Number lines: $line"
}
As for characters, try this (adjust echo "test" to where you get your output from):
expr `echo "test" | sed "s/./ + 1/g;s/^/0/"`
As for lines, try this:
expr `echo -e "test\ntest\ntest" | sed "s/^.*$/./" | tr -d "\n" | sed "s/./ + 1/g;s/^/0/"`
===
As for your code, you want something like this to count words (if you want to go at it completely raw):
while read line ; do
set $line ;
while true ; do
[ -z $1 ] && break
l=`expr $l + 1`
shift ;
done ;
done
You can do this with the following Bash shell script:
count=0
for var in `cat $1`
do
count=`echo $count+1 | bc`
done
echo $count

Resources