How to grep a matching pattern in all the lines in a file except the first two lines - bash

I have text file named contacts.txt with few rows populated. In this file first two rows are headers so I don't want to do anything in these two rows.
I want to get input from the user, search the contacts file, and display the matching rows. If go with tail piped to grep I get the exact data, but no headers. How can I get the header rows, too?
echo -n "Enter the value to search: ";read search1; echo -e "\n"
if [ $(tail -n +3 /root/scripts/contacts.txt | grep -i $search1 | wc -l) -eq 0 ]; then
echo -e "No matching rows found!!!! \n"
echo -n "To re-enter press r. To go back to main menu press any key: ";
read reenter;
echo -e "\n";
if [ "$reenter" == "R" ] || [ "$reenter" == "r" ]; then
remove_entry # calling a function
else
inputscan # calling a function
fi
else
echo -n "Number of Matching rows found:"; tail -n +3 /root/scripts/contacts.txt | grep -i $search1 | wc -l; echo -e "\n";
tail -n +3 /root/scripts/contacts.txt | grep -i "$search1" | column -t -s";";
fi

As you said your first two lines are header so modify your script like below to print the header as well:-
echo -n "Number of Matching rows found:"; tail -n +3 /root/scripts/contacts.txt | grep -i $search1 | wc -l; echo -e "\n";
#Print first two lines of the file
head -2 /root/scripts/contacts.txt
tail -n +3 /root/scripts/contacts.txt | grep -i "$search1" | column -t -s";";

Good morning, I made a few modifications to your script:
put the contacts.txt file path in a variable, to avoid having to type it over and over again (and it's good it if changes later!)
put the code that counts the number of matches in a variable, to avoid running that code twice
added a head -2 to output your header lines.
#!/bin/bash
echo -n "Enter the value to search: ";read search1; echo -e "\n"
contactsfile='/root/scripts/contacts.txt'
number_matching_rows=$(tail -n +3 $contactsfile | grep -i $search1 | wc -l)
if [ $number_matching_rows -eq 0 ]; then
echo -e "No matching rows found!!!! \n"
echo -n "To re-enter press r. To go back to main menu press any key: ";
read reenter;
echo -e "\n";
if [ "$reenter" == "R" ] || [ "$reenter" == "r" ]; then
remove_entry # calling a function
else
inputscan # calling a function
fi
else
echo "Number of Matching rows found: $number_matching_rows"
head -2 $contactsfile
tail -n +3 $contactsfile | grep -i "$search1" | column -t -s";";
fi

You can use head -2 and pipe the output of multiple commands by combining them in a curly brace block.
{ head -2 /root/scripts/contacts.txt;
tail -n +3 /root/scripts/contacts.txt | grep -i "$search1"; } | column -t -s";";

Related

Shell: Add string to the end of each line, which match the pattern. Filenames are given in another file

I'm still new to the shell and need some help.
I have a file stapel_old.
Also I have in the same directory files like english_old_sync, math_old_sync and vocabulary_old_sync.
The content of stapel_old is:
english
math
vocabulary
The content of e.g. english is:
basic_grammar.md
spelling.md
orthography.md
I want to manipulate all files which are given in stapel_old like in this example:
take the first line of stapel_old 'english', (after that math, and so on)
convert in this case english to english_old_sync, (or after that what is given in second line, e.g. math to math_old_sync)
search in english_old_sync line by line for the pattern '.md'
And append to each line after .md :::#a1
The result should be e.g. of english_old_sync:
basic_grammar.md:::#a1
spelling.md:::#a1
orthography.md:::#a1
of math_old_sync:
geometry.md:::#a1
fractions.md:::#a1
and so on. stapel_old should stay unchanged.
How can I realize that?
I tried with sed -n, while loop (while read -r line), and I'm feeling it's somehow the right way - but I still get errors and not the expected result after 4 hours inspecting and reading.
Thank you!
EDIT
Here is the working code (The files are stored in folder 'olddata'):
clear
echo -e "$(tput setaf 1)$(tput setab 7)Learning directories:$(tput sgr 0)\n"
# put here directories which should not become flashcards, command: | grep -v 'name_of_directory_which_not_to_learn1' | grep -v 'directory2'
ls ../ | grep -v 00_gliederungsverweise | grep -v 0_weiter | grep -v bibliothek | grep -v notizen | grep -v Obsidian | grep -v z_nicht_uni | tee olddata/stapel_old
# count folders
echo -ne "\nHow much different folders: " && wc -l olddata/stapel_old | cut -d' ' -f1 | tee -a olddata/stapel_old
echo -e "Are this learning directories correct? [j ODER y]--> yes; [Other]-->no\n"
read lernvz_korrekt
if [ "$lernvz_korrekt" = j ] || [ "$lernvz_korrekt" = y ];
then
read -n 1 -s -r -p "Learning directories correct. Press any key to continue..."
else
read -n 1 -s -r -p "Learning directories not correct, please change in line 4. Press any key to continue..."
exit
fi
echo -e "\n_____________________________\n$(tput setaf 6)$(tput setab 5)Found cards:$(tput sgr 0)$(tput setaf 6)\n"
#GET && WRITE FOLDER NAMES into olddata/stapel_old
anzahl_zeilen=$(cat olddata/stapel_old |& tail -1)
#GET NAMES of .md files of every stapel and write All to 'stapelname'_old_sync
i=0
name="var_$i"
for (( num=1; num <= $anzahl_zeilen; num++ ))
do
i="$((i + 1))"
name="var_$i"
name=$(cat olddata/stapel_old | sed -n "$num"p)
find ../$name/ -name '*.md' | grep -v trash | grep -v Obsidian | rev | cut -d'/' -f1 | rev | tee olddata/$name"_old_sync"
done
(tput sgr 0)
I tried to add:
input="olddata/stapel_old"
while IFS= read -r line
do
sed -n "$line"p olddata/stapel_old
done < "$input"
The code to change only the english_old_sync is:
lines=$(wc -l olddata/english_old_sync | cut -d' ' -f1)
for ((num=1; num <= $lines; num++))
do
content=$(sed -n "$num"p olddata/english_old_sync)
sed -i "s/"$content"/""$content":::#a1/g"" olddata/english_old_sync
done
So now, this need to be a inner for-loop, of a outer for-loop which holds the variable for english, right?
stapel_old should stay unchanged.
You could try a while + read loop and embed sed inside the loop.
#!/usr/bin/env bash
while IFS= read -r files; do
echo cp -v "$files" "${files}_old_sync" &&
echo sed '/^.*\.md$/s/$/:::#a1/' "${files}_old_sync"
done < olddata/staple_old
convert in this case english to english_old_sync, (or after that what is given in second line, e.g. math to math_old_sync)
cp copies the file with a new name, if the goal is renaming the original file name from the content of the file staple_old then change cp to mv
The -n and -i flag from sed was ommited , include it, if needed.
The script also assumes that there are no empty/blank lines in the content of staple_old file. If in case there are/is add an addition test after the line where the do is.
[[ -n $files ]] || continue
It also assumes that the content of staple_old are existing files. Just in case add an additional test.
[[ -e $files ]] || { printf >&2 '%s no such file or directory.\n' "$files"; continue; }
Or an if statement.
if [[ ! -e $files ]]; then
printf >&2 '%s no such file or directory\n' "$files"
continue
fi
See also help test
See also help continue
Combining them all together should be something like:
#!/usr/bin/env bash
while IFS= read -r files; do
[[ -n $files ]] || continue
[[ -e $files ]] || {
printf >&2 '%s no such file or directory.\n' "$files"
continue
}
echo cp -v "$files" "${files}_old_sync" &&
echo sed '/^.*\.md$/s/$/:::#a1/' "${files}_old_sync"
done < olddata/staple_old
Remove the echo's If you're satisfied with the output so the script could copy/rename and edit the files.

String comparison from nested for returns always false

The main issue is that i try to parse ls to do a mock "Compare directories" but when i do so since i use nested fors i cant properly compare the results from it since the comparison of two filenames/strings even if they are the same it always returns false
I tried erasing the white characters but no results.
var1=$(ls -l $1 | grep -v ^d | tail -n +2 | tr -s " "| cut -d " " -f 9)
var2=$(ls -l $2 | grep -v ^d | tail -n +2 | tr -s " "| cut -d " " -f 9)
for i in $var1 ; do
i=$(printf "$i" | tr -d '[:space:]')
flag=0
var3=$(ls -l $1 | grep -v ^d | tail -n +2 | tr -s " " | grep $i | cut -d " " -f 5)
for j in $var2 ; do
j=$(printf $j | tr -d '[:space:]')
var4=$(ls -l $2 | grep -v ^d | tail -n +2 | tr -s " " | grep $j | cut -d " " -f 5)
if [ "$i" == "$j" ] ; then
if [ "$var3" != "$var4" ] ; then
flag=1
fi
else
flag=1
fi
done
if [ $flag -eq 1 ] ; then
printf "$i file does not exist on the $2 catalog\n"
printf "It 's size is :$var3 \n"
let Sum=$Sum+$var3
fi
done
This is not a string comparison problem, it's a logic problem.
I wrote you a MCVE that demonstrates the same problem with less code and fewer dependencies:
flag=0
target="hello"
for candidate in "hello" "world"
do
if [ "$target" != "$candidate" ]
then
flag=1
fi
done
if [ "$flag" -eq 1 ]
then
echo "The string was not found"
fi
This prints The string was not found every time, just like your script, even though it's clearly there.
The problem here is that the script requires that ALL files match. It should only require that ANY file matches. The easiest way to fix this is to:
Set flag=1 when a MATCH is found (not a mismatch)
Make flag=1 signify that a match was found (rather than no match was found)
Here's the version which correctly finds the string:
flag=0
target="hello"
for candidate in "hello" "world"
do
if [ "$target" = "$candidate" ]
then
flag=1
fi
done
if [ "$flag" -eq 1 ]
then
echo "The string was found"
else
echo "The string was not found"
fi

Shell script: check if any files in one directory are newer than any files in another directory

I want to run a command in a shell script if files in one directory have changed more recently than files in another directory.
I would like something like this
if [ dir1/* <have been modified more recently than> dir2/* ]; then
echo 'We need to do some stuff!'
fi
As described in BashFAQ #3, broken down here into reusable functions:
newestFile() {
local latest file
for file; do
[[ $file && $file -nt $latest ]] || latest=$file
done
}
directoryHasNewerFilesThan() {
[[ "$(newestFile "$1"/*)" -nt "$(newestFile "$2" "$2"/*)" ]]
}
if directoryHasNewerFilesThan dir1 dir2; then
echo "We need to do something!"
else
echo "All is well"
fi
If you want to count the directories themselves as files, you can do that too; just replace "$(newestFile "$1"/*)" with "$(newestFile "$1" "$1"/*)", and likewise for the call to newestFile for $2.
Using /bin/ls
#!/usr/bin/ksh
dir1=$1
dir2=$2
#get modified time of directories
integer dir1latest=$(ls -ltd --time-style=+"%s" ${dir1} | head -n 2 | tail -n 1 | awk '{print $6}')
integer dir2latest=$(ls -ltd --time-style=+"%s" ${dir2} | head -n 2 | tail -n 1 | awk '{print $6}')
#get modified time of the latest file in the directories
integer dir1latestfile=$(ls -lt --time-style=+"%s" ${dir1} | head -n 2 | tail -n 1 | awk '{print $6}')
integer dir2latestfile=$(ls -lt --time-style=+"%s" ${dir2} | head -n 2 | tail -n 1 | awk '{print $6}')
#sort the times numerically and get the highest time
val=$(/bin/echo -e "${dir1latest}\n${dir2latest}\n${dir1latestfile}\n${dir2latestfile}" | sort -n | tail -n 1)
#check to which file the highest time belongs to
case $val in
#(${dir1latest}|${dir1latestfile})) echo $dir1 is latest ;;
#(${dir2latest}|${dir2latestfile})) echo $dir2 is latest ;;
esac
It's simple, get times stamps of both the folders in machine format(epoch time) then do simple comparison. that's all

if variable exists in col 1 of a file, set a separate variable equal to the corresponding value in col 2

I'm writing a script to run a grep for an input, create a file list and grep that file list in a separate directory and print the results.
echo "enter term"
read term
grep -rc "$term" /prod/directory1 | grep -v ":0" | sed -s 's%:[0-9]*%%' > file_list.txt
grep -rl --file=file_list.txt /tmp/directory2 > results.txt
but i would like to add an if statement that will identify if the $term is equal to a value in the first column of another file, and then set a new variable to the corresponding value in column 2 of that file.
echo "enter term"
read term
for i in products.txt; do
if [[ $term = $i ]]; then
var2 = $2
echo "product code set to: "$var2
else
var2 = 0
echo "product code set to 0."
fi
done
grep -rc "$term" /prod/directory1 | grep -v ":0" | sed -s 's%:[0-9]*%%' > file_list.txt
if [[ ! $var2 = 0 ]] ; then
grep -rc "var2" /prod/directory1 | grep -v ":0" | sed -s 's%:[0-9]*%%' >> file_list.txt
sed -s 's%.properties%%' file_list.txt | sort -u > file_list.txt
grep -rl --file=file_list.txt /tmp/directory2 > results.txt
the new grep will return results with .properties at the end, so I remove them and also any duplicate files from the list. Below is a sample for products.txt
Product_1 productCode_1
Product_2 productCode_2
Product_3 productCode_3
I would like the script to identify whether $term is in column one of that file, and then set $var2 equal to the corresponding productCode in column two. I believe once that variable is set everything else will work out nicely but right now it fails at:
for i in products.txt; do
if [[$term = $i ]]; then
var2 = $2
Thanks in advance for the assistance, I'm relatively new to bash scripting so I apologize in advance for my ineptitude.
Try:
var2=$(awk -v term="Product_2" '$1==term{print $2}' products.txt)
Change Product_2 to read from another variable, if required.

Too many arguments error in shell script

I am trying a simple shell script like the following:
#!/bin/bash
up_cap=$( cat result.txt | cut -d ":" -f 6,7 | sort -n | cut -d " " -f 2 | sort -n)
down_cap=$( cat result.txt | cut -d : -f 6,7 | sort -n | cut -d " " -f 6| sort -n)
for value in "${down_cap[#]}";do
if [ $value > 80000 ]; then
cat result.txt | grep -B 1 "$value"
fi
done
echo " All done, exiting"
when I execute the above script as ./script.sh, I get the error:
./script.sh: line 5: [: too many arguments
All done, exiting
I have googled enough, and still not able to rectify this.
You want
if [ "$value" -gt 80000 ]; then
You use -gt for checking if A is bigger than B, not >. The quotation marks I merely added to prevent the script from failing in case $value is empty.
Try to declare variable $value explicitly:
declare -i value
So, with the dominikh's and mine additions the code should look like this:
#!/bin/bash
up_cap=$( cat result.txt | cut -d ":" -f 6,7 | sort -n | cut -d " " -f 2 | sort -n)
down_cap=$( cat result.txt | cut -d : -f 6,7 | sort -n | cut -d " " -f 6| sort -n)
for value in "${down_cap[#]}";do
declare -i value
if [ $value -gt 80000 ]; then
cat result.txt | grep -B 1 "$value"
fi
done
echo " All done, exiting"

Resources