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?
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?
Odpowiedzi:
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 -z
wyś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 NUL
wpisy -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.
find -name \*.java | while read f; do tail -n1 $f | read -r _ || echo >> $f; done
git ls-files
którego nadal będziesz oszczędzać przed edycją plików, które nie są śledzone w kontroli wersji.
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: -d
opcja read
nie jest dostępna w POSIX sh.
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.
sed -i -e '$a\' file
I alternatywnie dla OS X sed
:
sed -i '' -e '$a\' file
Dodaje to \n
na 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
man sed
: $ Match the last line.
Ale może to działa tylko przypadkowo. Twoje rozwiązanie również działa.
$
pasuje do ostatniego wiersza, dlaczego nie dodaje kolejnej nowej linii do ciągu, który już zawiera nową linię?
$
. 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.
/regex/
nadaje mu inne znaczenie. Strony podręcznika FreeBSD są nieco bardziej pouczające, jak sądzę: freebsd.org/cgi/man.cgi?query=sed
Spójrz:
$ echo -n foo > foo
$ cat foo
foo$
$ echo "" >> foo
$ cat foo
foo
więc echo "" >> noeol-file
powinien 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)
""
nie jest konieczne (przynajmniej dla bash) i tail -1 | wc -l
mogą być wykorzystane, aby dowiedzieć się plik bez nowej linii na końcu
""
jest to konieczne do bash, ale widziałem echo
implementacje, 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-file
jest prawdopodobnie nieco bardziej wytrzymały. printf "\n" >> noeol-file
jest jeszcze bardziej.
csh
jest echo
to 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 rc
lub es
na przykład.
tcsh
, w przeciwieństwie do csh
, drukuje nowy wiersz, gdy jest wywoływany bez argumentów - niezależnie od ustawienia $echo_style
.
Inne rozwiązanie przy użyciu ed
. To rozwiązanie wpływa tylko na ostatni wiersz i tylko w przypadku \n
jego braku:
ed -s file <<< w
Zasadniczo działa otwieranie pliku do edycji za pomocą skryptu, skrypt jest pojedynczą w
komendą, 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.
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ą.
yash
jeś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).
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
echo ""
wydaje się bardziej solidny niż echo -n '\n'
. Lub możesz użyćprintf '\n'
Najszybszym rozwiązaniem jest:
[ -n "$(tail -c1 file)" ] && printf '\n' >>file
Jest naprawdę szybki.
W przypadku pliku średniej wielkości seq 99999999 >file
zajmuje 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
Działa w ash, bash, lksh, mksh, ksh93, attsh i zsh, ale nie yash.
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
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 0A
zapewni 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.
xxd
ani hexdump
narzędzia POSIX. W zestawie narzędzi POSIX jest od -An -tx1
wartość szesnastkowa bajtu.
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?
emacs
nie dodać nową linię na końcu pliku.
(setq require-final-newline 'ask)
w swoim.emacs
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.
cat file.csv | tr "\r" "\n" | { cat; echo; } | sed "/^[[:space:]]*$/d" | tail -n +2 | wc -l
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.
paste infile 1<> infile
.
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 ...
W vi
/ vim
/ ex
redaktorzy automatycznie dodać <EOL>
na EOF chyba że plik ma go już.
Więc spróbuj albo:
vi -ecwq foo.txt
co jest równoważne z:
ex -cwq foo.txt
Testowanie:
$ printf foo > foo.txt && wc foo.txt
0 1 3 foo.txt
$ ex -scwq foo.txt && wc foo.txt
1 1 4 foo.txt
Aby poprawić wiele plików, zaznacz: Jak naprawić „Brak nowej linii na końcu pliku” dla wielu plików? w SO
Dlaczego to takie ważne? Aby zachować zgodność naszych plików z POSIX .
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).
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\' {} \;
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.
grep '' file 1<> file
, ale nadal czyta i zapisuje plik w pełni.
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, wc
polecenie zwraca wartość 2
i piszemy nową linię.
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.
echo $'' >> <FILE_NAME>
doda pusty wiersz na końcu pliku.
echo $'\n\n' >> <FILE_NAME>
doda 3 puste linie na końcu pliku.
Jeśli plik jest zakończony zakończeniami linii systemu Windows\r\n
i jesteś w systemie Linux, możesz użyć tego sed
polecenia. Dodaje tylko \r\n
do 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\n
wyrażenie, wyrażenie regularne wyszukiwania nie będzie pasować, dlatego nic się nie wydarzy.
Możesz napisać fix-non-delimited-line
skrypt 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
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