Hoe gebruik je `while read` (Bash) om de laatste regel in een bestand te lezen als er geen nieuwe regel aan het einde van het bestand staat?

Stel dat ik het volgende Bash-script heb:

while read SCRIPT_SOURCE_LINE; do
  echo "$SCRIPT_SOURCE_LINE"
done

Ik heb gemerkt dat voor bestanden zonder een nieuwe regel aan het einde, dit de laatste regel in feite overslaat.

Ik heb gezocht naar een oplossing en vond dit:

Als het lezen in plaats daarvan het einde van het bestand bereikt
van end-of-line, staat er wel in de
gegevens en wijs deze toe aan de variabelen,
maar het wordt afgesloten met een status die niet nul is.
Als uw lus is geconstrueerd “terwijl
lezen ;doe dingen ;gedaan

Dus in plaats van de read exit te testen
status direct, test een vlag en heb
het leescommando zet die vlag van
binnen het luslichaam. Op die manier
ongeacht leest de exit-status, de
hele loop body wordt uitgevoerd, omdat read
was slechts een van de opdrachten in de lijst
in de lus als alle andere, niet a
beslissende factor of de lus zal
helemaal rennen.

DONE=false
until $DONE ;do
read || DONE=true
# process $REPLY here
done < /path/to/file.in

Hoe kan ik deze oplossing herschrijven zodat deze zich precies hetzelfde gedraagt ​​als de while-lus die ik eerder had, d.w.z. zonder de locatie van het invoerbestand hard te coderen?


Antwoord 1, autoriteit 100%

Ik gebruik de volgende constructie:

while IFS= read -r LINE || [[ -n "$LINE" ]]; do
    echo "$LINE"
done

Het werkt met vrijwel alles behalve null-tekens in de invoer:

  • Bestanden die beginnen of eindigen met lege regels
  • Regels die beginnen of eindigen met witruimte
  • Bestanden die geen eindigende nieuwe regel hebben

Antwoord 2, autoriteit 40%

In je eerste voorbeeld neem ik aan dat je leest uit stdin. Om hetzelfde te doen met het tweede codeblok, hoeft u alleen maar de omleiding te verwijderen en $REPLY te herhalen:

DONE=false
until $DONE ;do
read || DONE=true
echo $REPLY
done

Antwoord 3, autoriteit 12%

Gebruik grepmet while-lus:

while IFS= read -r line; do
  echo "$line"
done < <(grep "" file)

Als u grep .gebruikt in plaats van grep "", worden de lege regels overgeslagen.

Opmerking:

  1. Als u IFS=gebruikt, blijft elke regelinspringing intact.

  2. Je zou bijna altijd de -r optie met read moeten gebruiken.

  3. Bestand zonder een nieuwe regel aan het einde is geen standaard Unix-tekstbestand.


Antwoord 4, autoriteit 10%

Probeer in plaats van lezenGNU Coreutilste gebruiken zoals tee, cat, enz.

van stdin

readvalue=$(tee)
echo $readvalue

uit bestand

readvalue=$(cat filename)
echo $readvalue

Antwoord 5, autoriteit 5%

Dit is het patroon dat ik heb gebruikt:

while read -r; do
  echo "${REPLY}"
done
[[ ${REPLY} ]] && echo "${REPLY}"

Wat werkt omdat zelfs als de while-lus eindigt als de “test” van de readeindigt met een code die niet nul is, readvult nog steeds de ingebouwde variabele $REPLY(of welke variabelen je ook kiest om toe te wijzen met read).


Antwoord 6, autoriteit 2%

Het basisprobleem hier is dat readfoutniveau 1 retourneert wanneer het EOF tegenkomt, zelfs als het de variabele nog steeds correct invoert.

Je kunt het foutniveau van readdus meteen in je lus gebruiken, anders worden de laatste gegevens niet geparseerd. Maar je zou dit kunnen doen:

eof=
while [ -z "$eof" ]; do
    read SCRIPT_SOURCE_LINE || eof=true   ## detect eof, but have a last round
    echo "$SCRIPT_SOURCE_LINE"
done

Als je een zeer solide manier wilt om je regels te ontleden, gebruik dan:

IFS='' read -r LINE

Onthoud dat:

  • NUL-teken wordt genegeerd
  • als je vasthoudt aan het gebruik van echoom het gedrag van catna te bootsen, moet je een echo -nforceren nadat EOF is gedetecteerd ( je kunt de voorwaarde gebruiken [ "$eof" == true ])

Antwoord 7

Het antwoord van @Netcoder is goed, deze optimalisatie elimineert valse blanco regels, en zorgt er ook voor dat de laatste regel geen nieuwe regel heeft, als dat de oorspronkelijke regel was.

DONE=false
NL=
until $DONE ;do
if ! read ; then DONE=true ; NL='-n ';fi
echo $NL$REPLY
done

Ik heb een variant hiervan gebruikt om 2 functies te maken om tekst door te lijnen met een ‘[‘ om grep tevreden te houden. (u kunt andere vertalingen toevoegen)

function grepfix(){
    local x="$@";
    if [[ "$x" == '-' ]]; then
      local DONE=false
      local xx=
      until $DONE ;do
         if ! IFS= read ; then DONE=true ; xx="-n "; fi
         echo ${xx}${REPLY//\[/\\\[}
      done
    else
      echo "${x//\[/\\\[}"
    fi
 }
 function grepunfix(){
    local x="$@";
    if [[ "$x" == '-' ]]; then
      local DONE=false
      local xx=
      until $DONE ;do
         if ! IFS= read ; then DONE=true ; xx="-n "; fi
         echo ${xx}${REPLY//\\\[/\[}
      done
    else
      echo "${x//\\\[/\[}"
    fi
 }

(passen – aangezien $1 pipe mogelijk maakt, worden anders alleen argumenten vertaald)


Antwoord 8

voor het lezen van bestanden met of zonder een nieuwe regel aan het einde:

cat "somefile" | { cat ; echo ; } | while read line; do echo $line; done

Bron: Mijn open source-project https://sourceforge.net/ projects/command-output-to-html-table/

Other episodes