Bebop_ Bebop_ - 6 months ago 12
Bash Question

Write a line to a file, but also write previous line if it matches a pattern and exists

I'm trying to write a function which reads text from a large file and writes specific blocks of text to another file.

Example file

@Tag
Scenario 1:
Do thing 1
Do thing 2
Scenario 2:
Do thing 1
Do thing 3
@Tag2
Scenario 3:
Do thing 1
Don't do thing 4


I'm trying to read through this file line by line (using IFS right now) and want the output to be something like this:

File 1

@Tag
Scenario 1:
Do thing 1
Do thing 2


File 2

Scenario 2:
Do thing 1
Do thing 3


File 3

@Tag2
Scenario 3:
Do thing 1
Don't do thing 4


I have the pieces in place to read through the file and separate out on the "Scenario" pattern and the lines after it, but the problem I'm running into is trying to figure out how to capture the @Tag pattern and write it if it exists above the "Scenario" pattern.

Edit: here is the current relevant part of the script:

function writeToTestFile {
while IFS='' read -r line || [[ -n "$line" ]]; do
#if line matches the tag pattern of "@" followed by anything, store it
if [[ $line == *@* || "" ]]; then
local tagValue=$line

#if line in file matches "Scenario:" pattern, write to new file
elif [[ $line == *Scenario:* ]]; then
fileToWriteTo=$filename$counter$extention
((counter++))
echo "writing to $fileToWriteTo"
touch $dirToWriteTo/$fileToWriteTo

else
#if line does not match "Scenario:" pattern, check for existing file and write to that
if [[ -e $dirToWriteTo/$fileToWriteTo ]]; then
echo " "$line >> $dirToWriteTo/$fileToWriteTo
fi
# if file does not exist and line does match pattern, do nothing
fi

done < "$1"


}

Answer

You can parse the file fairly easily with your function in bash. The key is not to worry about looking for the tag lines. Simply look for Scenario, having checked/saved the previous tag line each iteration in some variable like tag. When Scenario is found, check if tag exists. If so, write the tagline held in tag before Scenario and then continue with normal writing of output.

#!/bin/bash

function writeToTestFile {
    [ -z "$1" ] && {    ## validate input
        printf "%s() error: insufficient input.\n" "$FUNCNAME"
        return 1
    }
    [ -r "$1" ] || {    ## validate file readable
        printf "%s() error: file not readable '%s'\n" "$FUNCNAME" "$1"
        return 1
    }
    local tag=""    ## use local declarations
    local line=""
    local num=""
    local fname=""
    while IFS='' read -r line || [ -n "$line" ]; do
        if [ "${line// */}" = "Scenario" ]; then    ## check Scenario
            num="${line/Scenario /}"                ## parse num
            fname="File_${num%:}.txt"               ## parse fname
            :> "$fname"                             ## truncate fname
            [ -n "$tag" ] && printf "%s\n" "$tag" > "$fname"  ## tagline
            printf "%s\n" "$line" >> "$fname"       ## write Scenario line
        fi  ## write normal lines & update tagline
        [ "${line:0:1}" = " " ] && printf "%s\n" "$line" >> "$fname"
        [ "${line:0:1}" = "@" ] && tag="$line" || tag=
    done < "$1"
    return 0
}

writeToTestFile "$1"

(note: File_X.txt is truncated before being written, adjust as required. If there is any chance a line, other than a tag line, begins with '@', you can further anchor the comparison with "${line:0:4}" = "@Tag")

Input File

$ cat tagfile.txt
@Tag
Scenario 1:
   Do thing 1
   Do thing 2
Scenario 2:
   Do thing 1
   Do thing 3
@Tag2
Scenario 3:
   Do thing 1
   Don't do thing 4

Use/Output

$ bash tags.sh tagfile.txt

Checking the output files:

$ cat File_1.txt
@Tag
Scenario 1:
   Do thing 1
   Do thing 2

$ cat File_2.txt
Scenario 2:
   Do thing 1
   Do thing 3

$ cat File_3.txt
@Tag2
Scenario 3:
   Do thing 1
   Don't do thing 4

Look it over and let me know if you have any questions.