简体   繁体   中英

Bash scripting: why is the last line missing from this file append?

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;

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

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM