I have a shell script that need to ask the user for 4 lines of input. Then I need to display the longest line that was entered, and then whole input is gotta go into the file.
That is what i got so far:
#!/bin/bash
lines=()
echo "Please enter 4 lines of text: "
for ((i=1; i<=4; i++)); do
IFS= read -p "" -r line && lines+=("$line")
done
echo "The longest line you entered was: "
max=0
for((i=0;i<4;i++)); do
len=${#lines}
if [[ len -gt max ]] ; then
max=$len
long="${lines}"
fi
done
echo longest line="${long}" length="${max}"
echo "I'm now putting the four lines you entered into a text file called \"mylines.txt\"..."
printf "%s\n" "${lines[#]}" > lines.txt
This is not happening for me, can you tell me what am I doing wrong?
Thanks
You can figure out longest line and length in first for loop:
#!/bin/bash
lines=()
max=0
echo "Please enter 4 lines of text: "
for ((i=1; i<=4; i++)); do
IFS= read -r line
lines+=("$line")
[[ ${#line} -gt $max ]] && { max=${#line}; long="$line"; }
done
echo longest line="${long}" length="${max}"
echo "I'm now putting the four lines you entered into a text file called \"mylines.txt\"..."
printf "%s\n" "${lines[#]}" > lines.txt
Using your exact Example you would just need to actually loop through the array by specifying an index $i when setting your len and long variables.
#!/bin/bash
lines=()
echo "Please enter 4 lines of text: "
for ((i=1; i<=4; i++)); do
IFS= read -p "" -r line && lines+=("$line")
done
echo "The longest line you entered was: "
max=0
for((i=0;i<4;i++)); do
#See how I added the [$i] this will allow you to get the length of each item in the array
len=${#lines[$i]}
if [[ len -gt max ]] ; then
max=$len
#This gets the item in the array to set the value of $long to it.
long="${lines[$i]}"
fi
done
echo longest line="${long}" length="${max}"
echo "I'm now putting the four lines you entered into a text file called \"mylines.txt\"..."
printf "%s\n" "${lines[#]}" > lines.txt
Outputs:
Please enter 4 lines of text:
one
two
three
four
The longest line you entered was:
longest line=three length=5
I'm now putting the four lines you entered into a text file called "mylines.txt"...
Related
Writing a code that quizzes a user with questions from a file, compares the user_input with the answers in a .txt file. Can't seem to wrap my head around why it doesn't compare the answers and does not increment the variable by 1 for each correct answer. Might it be because the script doesn't read the answer.txt file?
#!/bin/bash
clear
questions=$1
answers=$2
correct=0
wrong=0
while read line #IFS = internal field Seperator
do
echo
echo $line
echo
echo "Your Answer:\c"
read user_answer </dev/tty #reads answer from terminal
if [ "$user_answer = $answers" ]; then
correct =$((correct + 1))
fi
done < $questions
echo
echo "Correct Answers: $correct "
echo "Wrong Answers: $wrong" ```
Assume that both the question and answer files have the same number of lines. The while loop will read one line from each file at each iteration, using separate file descriptors for each file. Standard input will be left alone, to allow the user to enter an answer.
clear
questions=$1
answers=$2
correct=0
wrong=0
while IFS= read -r q <&3 # Read from descriptor 3
IFS= read -r a <&4 # Read from descriptor 4
do
echo
echo "$q"
echo
echo "Your Answer:\c"
IFS= read -r user_answer
if [ "$user_answer" = "$a" ]; then
echo "Correct!"
correct=$((correct + 1))
else
echo "Wrong!"
wrong=$((wrong + 1))
fi
done 3< "$questions" 4< "$answers" # Use 3 for questions, 4 for answers
echo
echo "Correct Answers: $correct "
echo "Wrong Answers: $wrong"
I'm trying to add up all numbers inputted by a user, however there is no limit on how many numbers a user can input for the addition. How do I code this in linux shell script?
I have this so far:
firstNumber=0
secondNumber=0
number=0
echo Please enter two numbers to add up
read firstNumber
read secondNumber
echo Would you like to keep adding numbers? YES OR NO
read answer
if answer = YES
then
echo Please add another number
read number
echo $(($firstNumber +$secondNumber + $number))
fi
while answer = NO
do
echo $(($firstNumber + $secondNumber))
done
as #dash-o recommended, a simple entry sequence ended with ENTER is the most simple approach:
#!/usr/bin/env sh
sum=0
echo "Please enter integer numbers to add, or just RETURN to end."
while read -r number && [ -n "$number" ]; do
if [ "$number" -eq "$number" ] 2>/dev/null; then
sum=$((sum + number))
echo "Sum is: $sum"
else
echo "$number is not a valid integer. Try again..." >&2
fi
done
Or to allow multiple integers entry per line:
#!/usr/bin/env sh
# Save the shell's options state
shelloptions="$(set +o)"
# Disable globbing to prevent filename expansion in parameters
set -o noglob
sum=0
echo "Please enter integer numbers to add, or RETURN to end."
# Read lines until empty REPLY
while read -r && [ -n "$REPLY" ]; do
# Split $REPLY as parameters
# Globbing is turned-off so filenames will not mess with entries
# shellcheck disable=SC2086 # Explicitly intended word splitting
set -- $REPLY
# Iterate numbers from the parameters array
for number in "$#"; do
# If $number is a valid integer
if [ "$number" -eq "$number" ] 2>/dev/null; then
sum=$((sum + number))
else
echo "$number is not a valid integer." >&2
fi
done
echo "Sum is: $sum"
done
# Restore the shell's options state
eval "$shelloptions"
I am trying to write a simple bash script in which it takes in a text file, loops through the file and tells me how many times a certain string appears in the file. I want to eventually use this for a custom log searcher (for instance, search for the words 'log in' in a particular log file, etc.), but am having some difficulty as I am relatively new to bash. I want to be able to quickly search different logs for different terms at my will and see how many times they occur. Everything works perfectly until I get down to my loops. I think that I am using grep wrong, but am unsure if that is the issue. My loop codes may seem a little strange because I have been at it for a while and have been constantly tweaking things. I have done a bunch of searching but I feel like I am the only one who has ever had this issue (hopefully not because it is incredibly simple and I just suck). Any and all help is greatly appreciated, thanks in advance everyone.
edit: I would like to account for every instance of the string and not just
one instance per line
#!/bin/bash
echo "This bash script counts the instances of a user-defined string in a file."
echo "Enter a file to search:"
read fileName
echo " "
echo $path
if [ -f "$fileName" ] || [ -d "$fileName" ]; then
echo "File Checker Complete: '$fileName' is a file."
echo " "
echo "Enter a string that you would like to count the occurances of in '$fileName'."
read stringChoice
echo " "
echo "You are looking for '$stringChoice'. Counting...."
#TRYING WITH A WHILE LOOP
count=0
cat $fileName | while read line
do
if echo $line | grep $stringChoice; then
count=$[ count + 1 ]
done
echo "Finished processing file"
#TRYING WITH A FOR LOOP
# count=0
# for i in $(cat $fileName); do
# echo $i
# if grep "$stringChoice"; then
# count=$[ $count + 1 ]
# echo $count
# fi
# done
if [ $count == 1 ] ; then
echo " "
echo "The string '$stringChoice' occurs $count time in '$fileName'."
elif [ $count > 1 ]; then
echo " "
echo "The string '$stringChoice' occurs $count times in '$fileName'."
fi
elif [ ! -f "$fileName" ]; then
echo "File does not exist, please enter the correct file name."
fi
To find and count all occurrences of a string, you could use grep -o which matches only the word instead of the entire line and pipe the result to wc
read string; grep -o "$string" yourfile.txt | wc -l
You made basic syntax error in the code. Also, the variable of count was never updating as the the while loop was being executed in a subshell and thus the updated count value was never reflecting back.
Please change your code to the following one to get desired result.
#!/bin/bash
echo "This bash script counts the instances of a user-defined string in a file."
echo "Enter a file to search:"
read fileName
echo " "
echo $path
if [ -f "$fileName" ] ; then
echo "File Checker Complete: '$fileName' is a file."
echo " "
echo "Enter a string that you would like to count the occurances of in '$fileName'."
read stringChoice
echo " "
echo "You are looking for '$stringChoice'. Counting...."
#TRYING WITH A WHILE LOOP
count=0
while read line
do
if echo $line | grep $stringChoice; then
count=`expr $count + 1`
fi
done < "$fileName"
echo "Finished processing file"
echo "The string '$stringChoice' occurs $count time in '$fileName'."
elif [ ! -f "$fileName" ]; then
echo "File does not exist, please enter the correct file name."
fi
I'm writing a bash script to read a set of files line by line and perform some edits. To begin with, I'm simply trying to move the files to backup locations and write them out as-is, to test the script is working. However, it is failing to copy the last line of each file. Here is the snippet:
while IFS= read -r line
do
echo "Line is ***$line***"
echo "$line" >> $POM
done < $POM.backup
I obviously want to preserve whitespace when I copy the files, which is why I have set the IFS to null. I can see from the output that the last line of each file is being read, but it never appears in the output.
I've also tried an alternative variation, which does print the last line, but adds a newline to it:
while IFS= read -r line || [ -n "$line" ]
do
echo "Line is ***$line***"
echo "$line" >> $POM
done < $POM.backup
What is the best way to do this do this read-write operation, to write the files exactly as they are, with the correct whitespace and no newlines added?
The command that is adding the line feed (LF) is not the read command, but the echo command. read does not return the line with the delimiter still attached to it; rather, it strips the delimiter off (that is, it strips it off if it was present in the line, IOW, if it just read a complete line).
So, to solve the problem, you have to use echo -n to avoid adding back the delimiter, but only when you have an incomplete line.
Secondly, I've found that when providing read with a NAME (in your case line), it trims leading and trailing whitespace, which I don't think you want. But this can be solved by not providing a NAME at all, and using the default return variable REPLY, which will preserve all whitespace.
So, this should work:
#!/bin/bash
inFile=in;
outFile=out;
rm -f "$outFile";
rc=0;
while [[ $rc -eq 0 ]]; do
read -r;
rc=$?;
if [[ $rc -eq 0 ]]; then ## complete line
echo "complete=\"$REPLY\"";
echo "$REPLY" >>"$outFile";
elif [[ -n "$REPLY" ]]; then ## incomplete line
echo "incomplete=\"$REPLY\"";
echo -n "$REPLY" >>"$outFile";
fi;
done <"$inFile";
exit 0;
Edit: Wow! Three excellent suggestions from Charles Duffy, here's an updated script:
#!/bin/bash
inFile=in;
outFile=out;
while { read -r; rc=$?; [[ $rc -eq 0 || -n "$REPLY" ]]; }; do
if [[ $rc -eq 0 ]]; then ## complete line
echo "complete=\"$REPLY\"";
printf '%s\n' "$REPLY" >&3;
else ## incomplete line
echo "incomplete=\"$REPLY\"";
printf '%s' "$REPLY" >&3;
fi;
done <"$inFile" 3>"$outFile";
exit 0;
After review i wonder if :
{
line=
while IFS= read -r line
do
echo "$line"
line=
done
echo -n "$line"
} <$INFILE >$OUTFILE
is juts not enough...
Here my initial proposal :
#!/bin/bash
INFILE=$1
if [[ -z $INFILE ]]
then
echo "[ERROR] missing input file" >&2
exit 2
fi
OUTFILE=$INFILE.processed
# a way to know if last line is complete or not :
lastline=$(tail -n 1 "$INFILE" | wc -l)
if [[ $lastline == 0 ]]
then
echo "[WARNING] last line is incomplete -" >&2
fi
# we add a newline ANYWAY if it was complete, end of file will be seen as ... empty.
echo | cat $INFILE - | {
first=1
while IFS= read -r line
do
if [[ $first == 1 ]]
then
echo "First Line is ***$line***" >&2
first=0
else
echo "Next Line is ***$line***" >&2
echo
fi
echo -n "$line"
done
} > $OUTFILE
if diff $OUTFILE $INFILE
then
echo "[OK]"
exit 0
else
echo "[KO] processed file differs from input"
exit 1
fi
Idea is to always add a newline at the end of file and to print newlines only BETWEEN lines that are read.
This should work for quite all text files given they are not containing 0 byte ie \0 character, in which case 0 char byte will be lost.
Initial test can be used to decided whether an incomplete text file is acceptable or not.
Add a new line if line is not a line. Like this:
while IFS= read -r line
do
echo "Line is ***$line***";
printf '%s' "$line" >&3;
if [[ ${line: -1} != '\n' ]]
then
printf '\n' >&3;
fi
done < $POM.backup 3>$POM
I have a file (FreshPIN.txt) contain lots of pin code in each line; I need a bash script to select one of the pin, print it out, and then remove it from the source file, adding it to end of another file (usedPIN.txt).
FreshpPIN.txt is like:
========
1111111111111111
2222222222222222
3333333333333333
....
nnnnnnnnnnnnnnnn
========
before it prints, I should be asked to enter a number from 0 to 31 and put the number in the command below:
at&g**00**=xtd*788*1111111111111111#
in above example at&g and =xtd*788* should be stable in all output commands.
fresh=FreshPIN.txt
used=usedPin.txt
echo "Please key in"
read key
pin=`head -1 "$fresh"`
printf '%s\n' "$pin" >>"$used"
sed -i~ 1d "$fresh"
printf 'at&g%s=xtd*788*%s\n' "$key" "$pin"
How about this?
#!/bin/bash
fresh=FreshpPIN.txt
used=usedPIN.txt
max=31
die() {
echo >&2 "$#"
exit 1
}
# Get a random pin
pin=$(sed -n '/[[:digit:]]\+/p' -- "$fresh" | shuf -n1)
[[ "$pin" ]] || die "No more pins in file \`$fresh'"
echo "Pin chosen: $pin"
# Prompt user:
while read -e -r -p "Enter a number between 0 and $max (q to quit): " n; do
if [[ "$n" = q ]]; then
echo "Aborting. Pin $pin remains in file \`$fresh'."
exit 0
elif [[ "$n" != +([[:digit:]]) ]]; then
echo "Not a valid number. Try again."
elif ((10#$n>max)); then
echo "Number must be between 0 and $max. Try again."
else
break
fi
done
# Guard if read fails (e.g., if user presses Ctrl-D)
[[ "$n" ]] || die "Something went wrong."
# Delete this pin from file
ed -s -- "$fresh" <<EOF
/^$pin\$/d
wq
EOF
# Save pin in file
printf >> "$used" "%s\n" "$pin"
# Output:
printf "at&g**%02d**=xtd*788*%s\n" "$((10#$n))" "$pin"
It's quite robust (the user must really enter a number between 0 and 31, and it won't be messed up if user enters, e.g., 09). Uses ed to delete old pin from file FreshpPIN.txt: very efficient (no auxiliary file or ugly stuff using sed -i). Uses good bash practice overall. Uses shuf to get a random pin (don't need to compute the number of lines and hack ugly stuff around to get a random pin). sed is used to select only pins from file FreshpPIN.txt, so you can leave your header, comment, etc. in there.