Jak dodać nowy wiersz na końcu pliku?


190

Używając systemów kontroli wersji denerwuje mnie hałas, gdy mówi diff No newline at end of file.

Zastanawiałem się więc: jak dodać nowy wiersz na końcu pliku, aby pozbyć się tych wiadomości?



1
Ładne rozwiązanie poniżej, które dezynfekuje wszystkie pliki rekurencyjnie. Odpowiedz przez @Patrick Oscity
Qwerty

1
Również echo bajtów do pliku .

Idąc dalej, redaktorzy tekstowi często dysponują opcjami zapewniającymi końcowy znak nowej linii, którego Ty i Twoi współpracownicy mogliby użyć do utrzymania czystości.
Nick T

Odpowiedzi:


44

Aby rekurencyjnie zdezynfekować projekt, używam tego oneliner:

git ls-files -z | while IFS= read -rd '' f; do tail -c1 < "$f" | read -r _ || echo >> "$f"; done

Wyjaśnienie:

  • git ls-files -zwyświetla listę plików w repozytorium. Przyjmuje opcjonalny wzorzec jako dodatkowy parametr, który może być przydatny w niektórych przypadkach, jeśli chcesz ograniczyć operację do niektórych plików / katalogów. Alternatywnie, możesz użyć find -print0 ...lub podobnych programów do wyświetlenia listy plików, których dotyczy problem - po prostu upewnij się, że emituje NULwpisy -delimitowane.

  • while IFS= read -rd '' f; do ... done iteruje wpisy, bezpiecznie obsługując nazwy plików, które zawierają białe znaki i / lub znaki nowej linii.

  • tail -c1 < "$f" odczytuje ostatni znak z pliku.

  • read -r _ kończy z niezerowym statusem wyjścia, jeśli brakuje końcowego znaku nowej linii.

  • || echo >> "$f" dołącza nowy wiersz do pliku, jeśli status wyjścia poprzedniego polecenia był niezerowy.


Możesz to zrobić w ten sposób, jeśli chcesz po prostu zdezynfekować podzbiór swoich plików:find -name \*.java | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
Per Lundberg

@ StéphaneChazelas dobre sugestie, postaram się włączyć to do mojej odpowiedzi.
Patrick Oscity

@PerLundberg możesz również przekazać wzór, do git ls-filesktórego nadal będziesz oszczędzać przed edycją plików, które nie są śledzone w kontroli wersji.
Patrick Oscity

@ StéphaneChazelas dodając IFS= do unset separator dobrze zachowuje otaczające białe znaki. Pozycje z zerowym zakończeniem są istotne tylko wtedy, gdy masz pliki lub katalogi z nową linią w nazwie, co wydaje się dość dalekosiężne, ale jest to bardziej poprawny sposób obsługi ogólnego przypadku, zgadzam się. Podobnie jak małe zastrzeżenie: -dopcja readnie jest dostępna w POSIX sh.
Patrick Oscity

Tak, stąd moje zsh / bash . Zobacz także moje zastosowanie w tail -n1 < "$f"celu uniknięcia problemów z nazwami plików, które zaczynają się od -( tail -n1 -- "$f"nie działa dla pliku o nazwie -). Możesz wyjaśnić, że odpowiedź jest teraz specyficzna dla zsh / bash.
Stéphane Chazelas

202

Proszę bardzo :

sed -i -e '$a\' file

I alternatywnie dla OS X sed:

sed -i '' -e '$a\' file

Dodaje to \nna końcu pliku tylko wtedy, gdy nie kończy się on na nowej linii. Więc jeśli uruchomisz go dwa razy, nie doda kolejnej nowej linii:

$ cd "$(mktemp -d)"
$ printf foo > test.txt
$ sed -e '$a\' test.txt > test-with-eol.txt
$ diff test*
1c1
< foo
\ No newline at end of file
---
> foo
$ echo $?
1
$ sed -e '$a\' test-with-eol.txt > test-still-with-one-eol.txt
$ diff test-with-eol.txt test-still-with-one-eol.txt
$ echo $?
0

1
@jwd: Od man sed: $ Match the last line.Ale może to działa tylko przypadkowo. Twoje rozwiązanie również działa.
l0b0

1
Twoje rozwiązanie jest również bardziej eleganckie, przetestowałem je i zatwierdziłem , ale jak to działa? Jeśli $pasuje do ostatniego wiersza, dlaczego nie dodaje kolejnej nowej linii do ciągu, który już zawiera nową linię?
l0b0

27
Istnieją dwa różne znaczenia $. W wyrażeniu regularnym, na przykład w formularzu /<regex>/, ma zwykłe znaczenie „dopasuj koniec linii”. W przeciwnym razie, użyty jako adres, sed nadaje mu specjalne znaczenie „ostatni wiersz w pliku”. Kod działa, ponieważ sed domyślnie dołącza nowy wiersz do jego wyniku, jeśli jeszcze go nie ma. Kod „$ a \” mówi tylko „dopasuj do ostatniego wiersza pliku i nic do niego nie dodawaj”. Ale domyślnie, sed dodaje nową linię do każdej linii, którą przetwarza (takiej jak ta $linia), jeśli jeszcze jej nie ma.
jwd

1
Jeśli chodzi o stronę podręcznika: cytat, do którego się odwołujesz, znajduje się w sekcji „Adresy”. Umieszczenie go w środku /regex/nadaje mu inne znaczenie. Strony podręcznika FreeBSD są nieco bardziej pouczające, jak sądzę: freebsd.org/cgi/man.cgi?query=sed
jwd

2
Jeśli plik kończy się na nowej linii, to go nie zmienia, ale przepisuje go i aktualizuje znacznik czasu. To może, ale nie musi mieć znaczenia.
Keith Thompson,

39

Spójrz:

$ echo -n foo > foo 
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo

więc echo "" >> noeol-filepowinien załatwić sprawę. (Czy chciałeś poprosić o zidentyfikowanie tych plików i ich naprawienie?)

edit usunął ""z echo "" >> foo(patrz komentarz @ yuyichao) edit2 dodał ""ponownie ( ale zobacz komentarz @Keith Thompson)


4
""nie jest konieczne (przynajmniej dla bash) i tail -1 | wc -lmogą być wykorzystane, aby dowiedzieć się plik bez nowej linii na końcu
yuyichao

5
@yuyichao: Nie ""jest to konieczne do bash, ale widziałem echoimplementacje, które nic nie drukują, gdy są wywoływane bez argumentów (chociaż żaden z tych, które mogę teraz znaleźć, nie robi tego). echo "" >> noeol-filejest prawdopodobnie nieco bardziej wytrzymały. printf "\n" >> noeol-filejest jeszcze bardziej.
Keith Thompson

2
@KeithThompson, cshjest echoto jeden znany wyjściowym nic, gdy nie przeszedł żadnego argumentu. Ale jeśli mamy zamiar wspierać non-Bourne'a jak muszle, powinniśmy zrobić to echo ''zamiast echo ""jak echo ""byłoby Ouput ""<newline>z rclub esna przykład.
Stéphane Chazelas,

1
@ StéphaneChazelas: I tcsh, w przeciwieństwie do csh, drukuje nowy wiersz, gdy jest wywoływany bez argumentów - niezależnie od ustawienia $echo_style.
Keith Thompson,

16

Inne rozwiązanie przy użyciu ed. To rozwiązanie wpływa tylko na ostatni wiersz i tylko w przypadku \njego braku:

ed -s file <<< w

Zasadniczo działa otwieranie pliku do edycji za pomocą skryptu, skrypt jest pojedynczą wkomendą, która zapisuje plik z powrotem na dysk. Opiera się na tym zdaniu znalezionym na ed(1)stronie podręcznika:

OGRANICZENIA
       (...)

       Jeśli plik tekstowy (niebinarny) nie jest zakończony znakiem nowej linii,
       następnie ed dołącza do odczytu / zapisu. W przypadku pliku binarnego
       plik, ed nie dołącza nowego wiersza do odczytu / zapisu.

1
To nie dodaje do mnie nowego wiersza.
Olhovsky,

4
Pracuje dla mnie; wypisuje nawet „dołączoną linię Newline” (ed-1.10-1 w Arch Linux).
Stefan Majewsky

12

Prostym, przenośnym, zgodnym z POSIX sposobem dodania nieobecnej, końcowej nowej linii do pliku tekstowego byłoby:

[ -n "$(tail -c1 file)" ] && echo >> file

To podejście nie musi czytać całego pliku; może po prostu szukać EOF i stamtąd pracować.

Podejście to nie musi również tworzyć plików tymczasowych za twoimi plecami (np. Sed -i), więc nie ma to wpływu na hardlink.

echo dołącza nowy wiersz do pliku tylko wtedy, gdy wynikiem podstawienia polecenia jest niepusty ciąg. Zauważ, że może się to zdarzyć tylko wtedy, gdy plik nie jest pusty, a ostatni bajt nie jest znakiem nowej linii.

Jeśli ostatnim bajtem pliku jest nowa linia, tail ją zwraca, a następnie zastępuje ją rozkaz; wynikiem jest pusty ciąg. Test -n kończy się niepowodzeniem i echo nie działa.

Jeśli plik jest pusty, wynikiem podstawienia polecenia jest również pusty ciąg znaków i ponownie echo nie działa. Jest to pożądane, ponieważ pusty plik nie jest niepoprawnym plikiem tekstowym, ani nie jest równoważny niepustemu plikowi tekstowemu z pustą linią.


1
Zauważ, że to nie działa, yashjeśli ostatni znak w pliku jest znakiem wielobajtowym (na przykład w ustawieniach narodowych UTF-8), lub jeśli ustawienia narodowe to C, a ostatni bajt w pliku ma ustawiony 8. bit. W przypadku innych powłok (oprócz zsh) nie dodawałby nowego wiersza, jeśli plik kończy się bajtem NUL (ale z drugiej strony oznaczałoby to, że dane wejściowe byłyby nietekstowe nawet po dodaniu nowego wiersza).
Stéphane Chazelas,

1
@ StéphaneChazelas Dodano rozwiązanie dla yash .
sorontar

1
Czy można uruchomić to dla każdego pliku w folderze i podfolderach?
Qwerty

12

Dodaj nową linię niezależnie od:

echo >> filename

Oto sposób sprawdzenia, czy nowa linia istnieje na końcu przed jej dodaniem, za pomocą Pythona:

f=filename; python -c "import sys; sys.exit(open(\"$f\").read().endswith('\n'))" && echo >> $f

1
Nie używałbym wersji Pythona w żadnej pętli z powodu wolnego czasu uruchamiania Pythona. Oczywiście możesz zrobić pętlę w pythonie, jeśli chcesz.
Kevin Cox,

2
Czas uruchamiania dla Pythona wynosi tutaj 0,03 sekundy. Czy naprawdę uważasz to za problematyczne?
Alexander

3
Czas uruchamiania ma znaczenie, jeśli wywołujesz Python w pętli, dlatego powiedziałem, że rozważ wykonanie pętli w Pythonie. Wtedy ponosisz koszty początkowe tylko raz. Dla mnie połowa kosztu uruchomienia to ponad połowa czasu całego sniptu, uważam, że to znaczne obciążenie. (Znowu nie ma znaczenia, jeśli robimy tylko niewielką liczbę plików)
Kevin Cox

2
echo ""wydaje się bardziej solidny niż echo -n '\n'. Lub możesz użyćprintf '\n'
Keith Thompson

2
To zadziałało dla mnie dobrze
Daniel Gomez Rico

8

Najszybszym rozwiązaniem jest:

[ -n "$(tail -c1 file)" ] && printf '\n' >>file 

  1. Jest naprawdę szybki.
    W przypadku pliku średniej wielkości seq 99999999 >filezajmuje to milisekundy.
    Inne rozwiązania zajmują dużo czasu:

    [ -n "$(tail -c1 file)" ] && printf '\n' >>file  0.013 sec
    vi -ecwq file                                    2.544 sec
    paste file 1<> file                             31.943 sec
    ed -s file <<< w                             1m  4.422 sec
    sed -i -e '$a\' file                         3m 20.931 sec
  2. Działa w ash, bash, lksh, mksh, ksh93, attsh i zsh, ale nie yash.

  3. Nie zmienia znacznika czasu pliku, jeśli nie ma potrzeby dodawania nowej linii.
    Wszystkie inne przedstawione tutaj rozwiązania zmieniają znacznik czasu pliku.
  4. Wszystkie powyższe rozwiązania są poprawne POSIX.

Jeśli potrzebujesz rozwiązania przenośnego do yash (i wszystkich innych powłok wymienionych powyżej), może być nieco bardziej skomplikowane:

f=file
if       [ "$(tail -c1 "$f"; echo x)" != "$(printf '\nx')" ]
then     printf '\n' >>"$f"
fi

7

Najszybszym sposobem sprawdzenia, czy ostatnim bajtem pliku jest nowa linia, jest odczyt tylko tego ostatniego bajtu. Można to zrobić tail -c1 file. Jednak uproszczony sposób sprawdzenia, czy wartość bajtu jest nową linią, w zależności od powłoki zwykle usuwanej końcowej nowej linii w rozszerzeniu komendy kończy się niepowodzeniem (na przykład) w yash, gdy ostatnim znakiem w pliku jest UTF- 8 wartości.

Prawidłowym, zgodnym z POSIX, wszystkim (rozsądnym) sposobem na sprawdzenie, czy ostatnim bajtem pliku jest nowa linia, jest użycie xxd lub hexdump:

tail -c1 file | xxd -u -p
tail -c1 file | hexdump -v -e '/1 "%02X"'

Następnie porównanie wyniku powyższego z 0Azapewni dokładny test.
Przydatne jest unikanie dodawania nowej linii do inaczej pustego pliku.
Plik, który nie zapewni ostatniego znaku 0A, oczywiście:

f=file
a=$(tail -c1 "$f" | hexdump -v -e '/1 "%02X"')
[ -s "$f" -a "$a" != "0A" ] && echo >> "$f"

Krótkie i słodkie. Zajmuje to bardzo mało czasu, ponieważ po prostu odczytuje ostatni bajt (szukaj do EOF). Nie ma znaczenia, czy plik jest duży. W razie potrzeby dodaj tylko jeden bajt.

Pliki tymczasowe nie są potrzebne ani używane. Nie dotyczy to linków twardych.

Jeśli ten test zostanie uruchomiony dwukrotnie, nie doda on nowej linii.


1
@crw Wierzę, że dodaje użytecznych informacji.
sorontar

2
Zauważ, że xxdani hexdumpnarzędzia POSIX. W zestawie narzędzi POSIX jest od -An -tx1wartość szesnastkowa bajtu.
Stéphane Chazelas

@ StéphaneChazelas Prześlij to jako odpowiedź; Przyjechałem tu, szukając tego komentarza zbyt wiele razy
kelvin,

@kelvin, zaktualizowałem swoją odpowiedź
Stéphane Chazelas,

Zauważ, że POSIX nie gwarantuje wartości LF równej 0x0a. Nadal istnieją systemy POSIX, w których tak nie jest (te oparte na EBCDIC), choć są one obecnie niezwykle rzadkie.
Stéphane Chazelas

4

Lepiej popraw edytor użytkownika, który ostatnio edytował plik. Jeśli jesteś ostatnią osobą, która edytowała plik - jakiego edytora używasz, domyślam się, że jest to partner tekstowy?


2
Vim jest redaktorem, o którym mowa. Ale ogólnie masz rację, nie powinienem tylko naprawiać objawów;)
k0pernikus

6
dla vima musisz zejść mu z drogi i wykonać taniec pliku binarnego przy zapisie, aby vim nie dodawał nowej linii na końcu pliku - po prostu nie rób tego. LUB, aby po prostu poprawić istniejące pliki, otwórz je w vimie i zapisz plik, a vim „naprawi” brakujący nowy wiersz dla ciebie (może być łatwo skrypty dla wielu plików)
AD7six

3
My emacsnie dodać nową linię na końcu pliku.
enzotib

2
Dzięki za komentarz @ AD7six, ciągle otrzymuję raporty fantomowe z różnic, gdy coś zatwierdzam, o tym, że oryginalny plik nie ma nowego wiersza na końcu. Bez względu na to, jak edytuję plik za pomocą vima, nie mogę tego zrobić, aby nie wstawiał tam nowej linii. Więc to tylko vim.
Steven Lu,

1
@enzotib: Mam (setq require-final-newline 'ask)w swoim.emacs
Keith Thompson

3

Jeśli chcesz tylko szybko dodać nowy wiersz podczas przetwarzania potoku, użyj tego:

outputting_program | { cat ; echo ; }

jest również zgodny z POSIX.

Następnie możesz oczywiście przekierować go do pliku.


2
Fakt, że mogę tego użyć w potoku, jest pomocny. To pozwala mi policzyć liczbę wierszy w pliku CSV, z wyłączeniem nagłówka. Pomaga to uzyskać dokładną liczbę wierszy w plikach systemu Windows, które nie kończą się znakiem nowej linii lub znakiem powrotu karetki. cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l
Kyle Tolle,

3

Pod warunkiem, że na wejściu nie ma wartości null:

paste - <>infile >&0

... wystarczyłoby zawsze dodawać nową linię do końca infilu, jeśli jeszcze go nie miał. I musi tylko odczytać plik wejściowy za jednym razem, aby go poprawnie.


To nie zadziała tak, ponieważ stdin i stdout korzystają z tego samego opisu otwartego pliku (więc kursor w pliku). Zamiast tego będziesz potrzebować paste infile 1<> infile.
Stéphane Chazelas,

2

Chociaż nie odpowiada bezpośrednio na pytanie, oto pokrewny skrypt, który napisałem, aby wykryć pliki, które nie kończą się na nowej linii. To jest bardzo szybkie.

find . -type f | # sort |        # sort file names if you like
/usr/bin/perl -lne '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

Skrypt perla czyta listę (opcjonalnie posortowanych) nazw plików ze standardowego wejścia i dla każdego pliku odczytuje ostatni bajt, aby ustalić, czy plik kończy się na nowej linii, czy nie. Jest bardzo szybki, ponieważ unika czytania całej zawartości każdego pliku. Wyprowadza jeden wiersz dla każdego pliku, który czyta, z prefiksem „error:”, jeśli wystąpi jakiś błąd, „empty:”, jeśli plik jest pusty (nie kończy się na nowej linii!), „EOL:” („end of linia ”), jeśli plik kończy się na nowej linii i„ bez EOL: ”, jeśli plik nie kończy się na nowej linii.

Uwaga: skrypt nie obsługuje nazw plików zawierających znaki nowej linii. Jeśli korzystasz z systemu GNU lub BSD, możesz obsłużyć wszystkie możliwe nazwy plików, dodając -print0 do znalezienia, -z do sortowania i -0 do perla, w następujący sposób:

find . -type f -print0 | sort -z |
/usr/bin/perl -ln0e '
   open FH, "<", $_ or do { print " error: $_"; next };
   $pos = sysseek FH, 0, 2;                     # seek to EOF
   if (!defined $pos)     { print " error: $_"; next }
   if ($pos == 0)         { print " empty: $_"; next }
   $pos = sysseek FH, -1, 1;                    # seek to last char
   if (!defined $pos)     { print " error: $_"; next }
   $cnt = sysread FH, $c, 1;
   if (!$cnt)             { print " error: $_"; next }
   if ($c eq "\n")        { print "   EOL: $_"; next }
   else                   { print "no EOL: $_"; next }
'

Oczywiście nadal musisz wymyślić sposób kodowania nazw plików z nowymi liniami w danych wyjściowych (pozostawionych jako ćwiczenie dla czytelnika).

W razie potrzeby dane wyjściowe można przefiltrować, aby dodać nowy wiersz do plików, które go nie mają, najprościej mówiąc

 echo >> "$filename"

Brak końcowej nowej linii może powodować błędy w skryptach, ponieważ niektóre wersje powłoki i innych narzędzi nie będą poprawnie obsługiwały brakującej nowej linii podczas czytania takiego pliku.

Z mojego doświadczenia wynika, że ​​brak ostatecznej nowej linii jest spowodowany używaniem różnych narzędzi systemu Windows do edycji plików. Nigdy nie widziałem, aby vim powodował brak końcowej nowej linii podczas edycji pliku, chociaż będzie raportował o takich plikach.

Wreszcie, istnieją znacznie krótsze (ale wolniejsze) skrypty, które mogą zapętlać wprowadzane nazwy plików, aby wydrukować te pliki, które nie kończą się na nowej linii, takie jak:

/usr/bin/perl -ne 'print "$ARGV\n" if /.\z/' -- FILE1 FILE2 ...


0

Aby zastosować zaakceptowaną odpowiedź do wszystkich plików w bieżącym katalogu (plus podkatalogach):

$ find . -type f -exec sed -i -e '$a\' {} \;

Działa to w systemie Linux (Ubuntu). W systemie OS X prawdopodobnie musisz użyć -i ''(niesprawdzony).


4
Zauważ, że find .zawiera listę wszystkich plików, w tym plików w .git. Aby wykluczyć:find . -type f -not -path './.git/*' -exec sed -i -e '$a\' {} \;
friederbluemle

Chciałbym przeczytać ten komentarz / pomyśleć o nim, zanim go uruchomiłem. No cóż.
kstev,

0

Przynajmniej w wersjach GNU po prostu grep ''lubawk 1 kanonizuje jego wejście, dodając końcowy nowy wiersz, jeśli jeszcze nie jest obecny. Kopiują plik w tym procesie, co zajmuje dużo czasu (ale źródło nie powinno być zbyt duże, aby odczytać?) I aktualizuje modtime, chyba że zrobisz coś takiego

 mv file old; grep '' <old >file; touch -r old file

(chociaż może to być w porządku w przypadku pliku, który się rejestrujesz, ponieważ go zmodyfikowałeś) i traci on linki dowiązania, uprawnienia domyślne i listy ACL itp., chyba że jesteś jeszcze bardziej ostrożny.


Lub po prostu grep '' file 1<> file, ale nadal czyta i zapisuje plik w pełni.
Stéphane Chazelas,

-1

Działa to w systemie AIX ksh:

lastchar=`tail -c 1 *filename*`
if [ `echo "$lastchar" | wc -c` -gt "1" ]
then
    echo "/n" >> *filename*
fi

W moim przypadku, jeśli w pliku brakuje nowej linii, wcpolecenie zwraca wartość 2i piszemy nową linię.


Informacje zwrotne będą pojawiać się w formie ocen pozytywnych lub negatywnych. W komentarzach zostaniesz poproszony o przedstawienie bardziej szczegółowych odpowiedzi / pytań, bez sensu proszenia o nie w treści odpowiedzi. Przejdź do rzeczy, witaj w stackexchange!
k0pernikus

-1

Dodając do odpowiedzi Patricka Oscity , jeśli chcesz po prostu zastosować ją do określonego katalogu, możesz również użyć:

find -type f | while read f; do tail -n1 $f | read -r _ || echo >> $f; done

Uruchom to w katalogu, do którego chcesz dodać nowe wiersze.


-1

echo $'' >> <FILE_NAME> doda pusty wiersz na końcu pliku.

echo $'\n\n' >> <FILE_NAME> doda 3 puste linie na końcu pliku.


StackExchange ma zabawne formatowanie, naprawiłem to dla ciebie :-)
Peter

-1

Jeśli plik jest zakończony zakończeniami linii systemu Windows\r\n i jesteś w systemie Linux, możesz użyć tego sedpolecenia. Dodaje tylko \r\ndo ostatniego wiersza, jeśli jeszcze go nie ma:

sed -i -e '$s/\([^\r]\)$/\1\r\n/'

Wyjaśnienie:

-i    replace in place
-e    script to run
$     matches last line of a file
s     substitute
\([^\r]\)$    search the last character in the line which is not a \r
\1\r\n    replace it with itself and add \r\n

Jeśli ostatni wiersz zawiera już \r\nwyrażenie, wyrażenie regularne wyszukiwania nie będzie pasować, dlatego nic się nie wydarzy.


-1

Możesz napisać fix-non-delimited-lineskrypt taki jak:

#! /bin/zsh -
zmodload zsh/system || exit
ret=0
for file do
  if sysopen -rwu0 -- "$file"; then
    if sysseek -w end -1; then
      read -r x || print -u0
    else
      syserror -p "Can't seek in $file before the last byte: "
      ret=1
    fi
  else
    ret=1
  fi
done
exit $ret

W przeciwieństwie do niektórych podanych tutaj rozwiązań, to

  • powinien być wydajny, ponieważ nie rozwidla żadnego procesu, odczytuje tylko jeden bajt dla każdego pliku i nie przepisuje pliku ponownie (po prostu dodaje nową linię)
  • nie będzie przerywać dowiązań symbolicznych / dowiązań twardych ani wpływać na metadane (również ctime / mtime są aktualizowane tylko po dodaniu nowej linii)
  • powinien działać OK, nawet jeśli ostatni bajt ma wartość NUL lub jest częścią znaku wielobajtowego.
  • powinien działać OK bez względu na to, jakie znaki lub znaki inne mogą zawierać nazwy plików
  • Powinien obsługiwać poprawnie nieczytelne lub niemożliwe do odczytania lub niewidzialne pliki (i odpowiednio zgłaszać błędy)
  • Nie powinien dodawać nowego wiersza do pustych plików (ale w tym przypadku zgłasza błąd dotyczący nieprawidłowego wyszukiwania)

Możesz go użyć na przykład jako:

that-script *.txt

lub:

git ls-files -z | xargs -0 that-script

POSIXly, możesz zrobić coś funkcjonalnie równoważnego

export LC_ALL=C
ret=0
for file do
  [ -s "$file" ] || continue
  {
    c=$(tail -c 1 | od -An -vtc)
    case $c in
      (*'\n'*) ;;
      (*[![:space:]]*) printf '\n' >&0 || ret=$?;;
      (*) ret=1;; # tail likely failed
    esac
  } 0<> "$file" || ret=$? # record failure to open
done
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.