[SOLVED] Using sed to search and stop at a blank line
ProgrammingThis forum is for all programming questions.
The question does not have to be directly related to Linux and any language is fair game.
Notices
Welcome to LinuxQuestions.org, a friendly and active Linux Community.
You are currently viewing LQ as a guest. By joining our community you will have the ability to post topics, receive our newsletter, use the advanced search, subscribe to threads and access many other special features. Registration is quick, simple and absolutely free. Join our community today!
Note that registered members see fewer ads, and ContentLink is completely disabled once you log in.
If you have any problems with the registration process or your account login, please contact us. If you need to reset your password, click here.
Having a problem logging in? Please visit this page to clear all LQ-related cookies.
Get a virtual cloud desktop with the Linux distro that you want in less than five minutes with Shells! With over 10 pre-installed distros to choose from, the worry-free installation life is here! Whether you are a digital nomad or just looking for flexibility, Shells can put your Linux machine on the device that you want to use.
Exclusive for LQ members, get up to 45% off per month. Click here for more info.
I'm using a sed statement within a bash shell script to search through a file and stop when it reaches a blank line.
The sed statement is working, but I'm having trouble understanding how. (I found it online somewhere).
Code:
"sed -n "\?$i?,\?^$\|pattern?p"
A few things to note:
1. Variable $i is coming from a while loop.
2. I'm using the "?" as a delimiter so sed doesn't choke on special characters which may be used in the strings it's searching.
3. I understand using the "," as a range, but am having trouble understanding why the "?" after "$i" would not be commented-out with a "\" and also why the "|pattern" text is there.
4. I understand using "-n" to suppress output, then using "p" to print only what is returned from sed.
If someone could help break this down for me, I would appreciate it.
Here is the original sed statement before I modified it:
The final example makes sense but I am at a loss how your first would work?? My first issue would be the incorrect number of quotes and why the line would start with them?
Secondly, it is my understanding that sed only allows the changing of the delimiter the following, s/// ... so s??? could be used. On a quick test of a file here it definitely does not work
for me to have "?" as the delimiter for a range.
@grail. Yes, you can change the delimiter of the address regex if you prefix the first delimiting character with a backslash, as in this case (\?regex?). It's in the man page.
Now lets try breaking down the command, minus the delimiters (and assuming the first quote mark is just a typo):
Code:
sed -n
$i #address 1
,
^$\|pattern #address 2
p #command
The first address is your "$i" variable, naturally.
The second address is a complex regex. "|" is the "or" separator, enabled by prefixing it with a backslash because you're still in basic regex mode. If you used the "-r" option to enter extended regex mode, the backslash becomes unnecessary*.
So range 2 is either "pattern" or "^$", a blank line.
All told, it prints every line from "$i" to either the first instance of "pattern" or the first blank line.
*See the appropriate section of the grep man page for more details on basic vs. extended regex.
Edit: @daniel, I really hate seeing multiple commands chained together when one can do the job. In this case replace "q" with "Q" and it will exit before printing the last line.
Code:
sed '/^ *$/Q' $InFile
Unfortunately, this won't work if you need to start printing from any line other than the first though. Speaking of which, ed would do the job easily, thanks to its ability to designate relative line positions.
Thanks everyone - yes, sorry, the first double-quote before sed is a typo.
Thanks to David H for breaking this down, makes more sense now.
The file i'm searching through is formatted like this:
"string1"
"choice1"
"choice2"
"choice3"
"string2"
"choice1"
"choice2"
"choice3"
So what I am doing is searching for each "stringx" and grabbing it, plus its following choices, down to the blank line, because that is where the list ends and the next string begins. Then for each string + choices found in the source file, I'm writing those to a new file. The actual source file can contain hundreds of entries like above.
Edit: @daniel, I really hate seeing multiple commands chained together when one can do the job. In this case replace "q" with "Q" and it will exit before printing the last line.
Code:
sed '/^ *$/Q' $InFile
Perfect! Technical intuition suggested this could be done but I couldn't find the Q. Thank you!
Daniel B. Martin
Last edited by danielbmartin; 03-15-2013 at 11:17 AM.
Reason: Correct wording
So what I am doing is searching for each "stringx" and grabbing it, plus its following choices, down to the blank line, because that is where the list ends and the next string begins. Then for each string + choices found in the source file, I'm writing those to a new file. The actual source file can contain hundreds of entries like above.
Oh, well if that's what you want, consider using the csplit utility instead (it's part of the coreutils). It splits text into multiple files based on patterns or numbers of lines.
Read the info page for the full details on how it's used.
The only problem with the above is that the blank lines are still left in the new files. But a simple bit of post-processing with sed can remove those.
Code:
for fname in file*.txt; do sed -i '/^$/d' "$fname"; done
So what I am doing is searching for each "stringx" and grabbing it, plus its following choices, down to the blank line, because that is where the list ends and the next string begins. Then for each string + choices found in the source file, I'm writing those to a new file. The actual source file can contain hundreds of entries like above.
#!/bin/bash Daniel B. Martin Mar13
#
# To execute this program, launch a terminal session and enter:
# bash /home/daniel/Desktop/LQfiles/dbm684.bin
#
# This program inspired by:
# http://www.linuxquestions.org/questions/programming-9/
# using-sed-to-search-and-stop-at-a-blank-line-4175454081/
# File identification
Path=$(cut -d'.' -f1 <<< ${0})
InFile=$Path"inp.txt"
echo
echo "Method of LQ member danielbmartin #1"
# Blow away any leftover output files.
rm -f $Path'out'*'.txt'
k=1
while read line
do
if [[ "$line" == "" ]]; then let k=$k+1
else echo $line >> $Path'out'$k'.txt'
fi
done < $InFile
echo; echo "Normal end of job."; echo
exit
... produced four subset output files, as specified.
while read line ; do
case $line in
'') exit ;;
*) echo $line >> wherever ;;
esac
done < $InFile
A case statement is probably faster than using '[[' or 'test' builtins -and also faster than a pipe through sed (twice!) for small files. sed gives me headaches...
while read line ; do
case $line in
'') exit ;;
*) echo $line >> wherever ;;
esac
done < $InFile
Did you test this? I don't see any code which modifies the "wherever." Without that, all output goes into the same file. I modified your code thusly ...
Code:
k=1
while read line ; do
case $line in
'') let k=$k+1 ;;
*) echo $line >> $Path'out'$k'.txt' ;;
esac
done < $InFile
I don't see why the output file name needs to be modified by a counter. I guess I'm missing something -I thought the idea was to "stop when it reaches a blank line". Either way, the case statement will be faster than [[ or test.
@gnashley - your original idea was correct for the first post , but as of post #5 the OP has now asked that each part of the file be entered into separate files
LinuxQuestions.org is looking for people interested in writing
Editorials, Articles, Reviews, and more. If you'd like to contribute
content, let us know.