To remove blank lines from a file while preserving specific patterns, use awk with the condition NF || /pattern/. This approach prints every line that has fields or matches your target regex. It is efficient, standard on Linux, and handles most text processing tasks without external dependencies.
Understanding the Problem Scope
Text files often accumulate empty lines due to formatting errors, concatenation of multiple sources, or logging quirks. While simple removal of all blank lines is straightforward, the requirement to keep certain non-blank lines based on a pattern adds complexity. A naive approach might delete everything that looks empty, but if you need to preserve lines that match a specific signature (like a header or a status code), you must be precise. The core challenge lies in distinguishing between a truly empty line and a line that contains only whitespace versus a line that contains data relevant to your pattern. In many scripting scenarios, "blank" means zero characters or only spaces/tabs. However, if your pattern matching logic is flawed, you risk deleting critical data or leaving unwanted gaps. The solution requires a tool that can evaluate both the structural emptiness of a line and its content against a regular expression simultaneously.The Correct Awk Solution
The most robust method usesawk, a stream editor designed for this exact purpose. The command structure relies on two conditions joined by an OR operator. The first part, NF, checks if the number of fields is greater than zero. In awk, a line with no fields is considered false, which typically corresponds to empty lines or lines containing only whitespace (depending on the field separator). The second part, /pattern/, checks if the current line matches your specific regular expression.
Here is the correct syntax:
awk 'NF || /your_pattern/' input.txt > output.txt
This command reads input.txt and writes the result to output.txt. If you wish to edit the file in place, you can use GNU awk's in-place editing feature:
awk -i inplace 'NF || /your_pattern/' input.txt
It is crucial to understand why this works. If a line is not blank (NF is true), it is printed. If a line is blank (NF is false) but matches your pattern, it is also printed. Only lines that are both blank AND do not match the pattern are suppressed. This logical structure prevents the common error of deleting valid pattern-matching lines that happen to be formatted as blanks in some edge cases, or conversely, keeping garbage lines that don't match.
For more complex scenarios where you need to handle whitespace-only lines differently, you might adjust the field separator. By default, awk splits on whitespace. If your "blank" lines contain tabs that should be ignored, the default behavior usually suffices. However, if you define a custom separator, ensure it aligns with your definition of "empty."
Handling Whitespace-Only Lines
Sometimes, a line appears blank but contains spaces or tabs. TheNF check in awk naturally ignores these because they do not constitute fields when using the default field separator. Therefore, NF effectively treats whitespace-only lines as empty. If you need to preserve lines that contain only spaces but match a pattern, the same logic applies. The /pattern/ part will catch them. If they do not match, they are removed. This is generally the desired behavior for cleaning up log files or configuration dumps.
If you require stricter control over what constitutes "blank," such as allowing lines with exactly three spaces, you would need a more complex awk script involving gsub to strip whitespace before checking NF. However, for 95% of use cases, the simple NF || /pattern/ idiom is sufficient and preferred for its readability and performance.
Alternative Tools and Limitations
Whilesed is another popular tool, removing blank lines while keeping specific patterns is more verbose. You could use sed with address ranges, but managing the logic for "keep if matches" becomes messy. For example:
sed '/^$/d; /your_pattern/!d'
This sed command deletes empty lines first, then deletes any remaining line that does not match the pattern. Wait, this logic is flawed. If you delete empty lines first, you cannot easily distinguish between an originally empty line and a non-empty line that was deleted. A better sed approach is:
sed '/^[[:space:]]*$/b; /your_pattern/p; d'
This is significantly harder to read and debug than the awk equivalent. grep is another option, but it lacks the ability to print non-matching lines unless you invert the match, which contradicts the goal of keeping non-blank lines. grep -v '^$' | grep -E 'pattern|' is inefficient and creates intermediate processes. Therefore, awk remains the superior choice for this specific combination of filtering criteria.
When working with large files, awk is memory efficient as it processes line by line. There is no need to load the entire file into RAM. This makes it suitable for processing gigabyte-sized logs where performance matters.