1
1
#! /usr/bin/env bash
2
2
#
3
3
# Script Name: assert_last_line_empty.sh
4
- # Description: Ensures that files end with exactly one trailing empty line.
5
- # “Empty” here means a line that is either completely empty
6
- # or contains only whitespace. In particular, if there are multiple
7
- # trailing empty lines (or lines with only spaces/tabs), they will be
8
- # squashed to a single trailing empty line.
4
+ # Description: Ensures that text files end with exactly one trailing newline,
5
+ # collapsing multiple trailing empty lines (containing only
6
+ # whitespace/tabs/newlines) down to a single newline. Files
7
+ # that are empty (0 bytes) are unchanged; files containing only
8
+ # whitespace become a single blank line; binary files are skipped .
9
9
#
10
- # Usage: assert_last_line_empty.sh [--check] <file_or_directory_path>
10
+ # Usage: assert_last_line_empty.sh [--check] <file_or_directory_path>
11
+ # --check : Only check and report problems; do not change files.
12
+ # <file_or_directory_path> : A file or directory to process.
11
13
#
12
- # --check : Only check and report problems; do not change files.
13
- # <file_or_directory_path> : a file or directory to process.
14
- #
15
- # Example: ./assert_last_line_empty.sh --check path/to/file.txt
14
+ # Example: ./assert_last_line_empty.sh --check path/to/file.txt
16
15
#
17
16
18
- checkonly=0 # Flag indicating if we're only checking (no fixes)
19
- status=0 # Global exit status to track if any file needs fixing
17
+ checkonly=0 # Flag to indicate we only check (no fixes)
18
+ status=0 # Will become 1 if any file needs changes in --check mode
20
19
21
20
# ##############################################################################
22
- # normalize_file_content: outputs the normalized content for a file:
23
- # 1. If the file is empty (0 bytes), we output it as-is (no changes).
24
- # 2. If the file is non-empty but contains only whitespace/newlines,
25
- # we normalize it to exactly one empty line.
26
- # 3. Otherwise, we remove trailing whitespace on every line, remove all
27
- # trailing blank lines, and then add exactly one trailing newline.
21
+ # normalize_file_content
22
+ # - If empty file (0 bytes), output as is (no change).
23
+ # - If file contains only whitespace, collapse to a single newline.
24
+ # - Otherwise (it has non-whitespace):
25
+ # * Remove trailing spaces/tabs on each line.
26
+ # * If file has no newline at all, add exactly one.
27
+ # * If file has at least one newline, remove any extra trailing
28
+ # blank lines (including whitespace-only lines) so that we
29
+ # end with exactly one newline.
28
30
# ##############################################################################
29
31
normalize_file_content () {
30
32
local file=" $1 "
31
33
32
- # If the file is empty (0 bytes) , do nothing:
34
+ # 1) If empty, do nothing:
33
35
if [ ! -s " $file " ]; then
34
- cat " $file "
36
+ cat " $file " # or just 'return' if you prefer truly empty
35
37
return
36
38
fi
37
39
38
- # Check if the file has any non- whitespace character :
40
+ # 2) If file is all whitespace (spaces/tabs/newlines) :
39
41
if ! grep -q ' [^[:space:]]' " $file " 2> /dev/null; then
40
- # The file contains only whitespace / newlines => normalize to one empty line
42
+ # Collapse it to a single newline:
41
43
printf " \n"
42
- else
43
- # 1) Remove trailing spaces/tabs from each line
44
- # 2) Remove all trailing blank lines
45
- # 3) Add exactly one newline at the end
46
- sed ' s/[ \t]*$//' " $file " \
47
- | sed -e ' :a' -e ' /^$/{$d;N;ba}' \
48
- ; printf " \n"
44
+ return
49
45
fi
46
+
47
+ # 3) If the file has no newline at all:
48
+ if ! grep -q $' \n ' " $file " ; then
49
+ # Remove trailing spaces/tabs, then add exactly one newline:
50
+ sed ' s/[ \t]*$//' " $file "
51
+ printf " \n"
52
+ return
53
+ fi
54
+
55
+ # 4) File has non-whitespace *and* at least one newline:
56
+ # - Remove trailing spaces/tabs from each line
57
+ # - Remove all trailing empty lines
58
+ # - End with exactly one newline
59
+ sed ' s/[ \t]*$//' " $file " \
60
+ | sed -e ' :a' -e ' /^[[:space:]]*$/N; /^\n*$/ba'
61
+ printf " \n"
50
62
}
51
63
52
64
# ##############################################################################
53
- # assert_last_line_empty: Checks (and optionally fixes) a single file.
65
+ # assert_last_line_empty
66
+ # - Checks (and optionally fixes) a single file.
67
+ # - Skips binary files (by checking if grep thinks it is binary).
68
+ # - Logs changes.
54
69
# ##############################################################################
55
70
assert_last_line_empty () {
56
71
local file=" $1 "
57
- echo " Processing file: ${ file} "
72
+ echo " Processing file: $file "
58
73
59
- if [ ! -f " ${file} " ]; then
60
- echo " Error: ${file} is not a regular file."
74
+ # Skip if not a regular file:
75
+ if [ ! -f " $file " ]; then
76
+ echo " [ERROR] Not a regular file: $file "
61
77
return 1
62
78
fi
63
79
64
- # Create a temporary normalized version of the file
65
- local norm
66
- norm=" $( mktemp) "
80
+ # Skip if binary:
81
+ if ! grep -Iq . " $file " ; then
82
+ echo " [SKIP] Binary file (not modified): $file "
83
+ return 0
84
+ fi
67
85
68
- normalize_file_content " ${file} " > " ${norm} "
86
+ # Create temporary normalized version of the file
87
+ local tmp norm
88
+ tmp=" $( mktemp -t assert_last_line_empty.XXXXXX) "
69
89
70
- if [ $checkonly -eq 1 ]; then
71
- # Compare the normalized file with the original
72
- if diff -q " ${file} " " ${norm} " > /dev/null; then
73
- echo " Already normalized: ${file} "
74
- else
75
- echo " Normalization required: ${file} "
76
- status=1
77
- fi
78
- rm -f " ${norm} "
90
+ normalize_file_content " $file " > " $tmp "
91
+
92
+ # Compare to see if changes needed:
93
+ if diff -q " $file " " $tmp " > /dev/null; then
94
+ echo " [OK] No changes needed: $file "
95
+ rm -f " $tmp "
79
96
else
80
- # In fix mode, overwrite the file only if there is a change
81
- if diff -q " ${file} " " ${norm} " > /dev/null ; then
82
- echo " No changes needed: ${file} "
83
- rm -f " ${norm} "
97
+ if [ " $checkonly " -eq 1 ] ; then
98
+ echo " [CHECK] Normalization required: $file "
99
+ status=1
100
+ rm -f " $tmp "
84
101
else
85
- mv " ${norm} " " ${ file} "
86
- echo " File fixed : ${ file} "
102
+ mv " $tmp " " $file "
103
+ echo " [FIXED] File updated : $file "
87
104
fi
88
105
fi
89
106
}
90
107
91
108
# ##############################################################################
92
- # process_file: Wrapper to normalize a single file.
93
- # ##############################################################################
94
- process_file () {
95
- local file=" $1 "
96
- assert_last_line_empty " ${file} "
97
- }
98
-
99
- # ##############################################################################
100
- # process_directory: Recursively process all files in a directory.
109
+ # process_directory
110
+ # - Recursively process all regular files in a directory.
101
111
# ##############################################################################
102
112
process_directory () {
103
113
local directory=" $1 "
104
114
105
- if [ ! -d " ${ directory} " ]; then
106
- echo " Error: ${ directory} is not a directory."
115
+ if [ ! -d " $directory " ]; then
116
+ echo " Error: $directory is not a directory."
107
117
return 1
108
118
fi
109
119
110
- echo " Processing directory: ${ directory} "
111
- # Use find to locate all regular files
112
- while IFS= read -r -d ' ' file; do
113
- process_file " ${ file} "
114
- done < <( find " ${directory} " -type f -print0 )
120
+ echo " Recursively processing directory: $directory "
121
+ find " $directory " -type f -print0 2> /dev/null \
122
+ | while IFS= read -r -d ' ' file; do
123
+ assert_last_line_empty " $file "
124
+ done
115
125
}
116
126
117
127
# ##############################################################################
118
- # main: Script entry point
128
+ # main: entry point
119
129
# ##############################################################################
120
130
main () {
121
131
if [ $# -eq 0 ]; then
122
- echo " Error: No path provided."
123
132
echo " Usage: $0 [--check] <file_or_directory_path>"
124
133
exit 1
125
134
fi
@@ -130,20 +139,17 @@ main() {
130
139
fi
131
140
132
141
local path=" $1 "
133
-
134
- if [ -d " ${path} " ]; then
135
- process_directory " ${path} "
136
- elif [ -f " ${path} " ]; then
137
- process_file " ${path} "
142
+ if [ -d " $path " ]; then
143
+ process_directory " $path "
144
+ elif [ -f " $path " ]; then
145
+ assert_last_line_empty " $path "
138
146
else
139
- echo " Error: ${ path} is not a valid file or directory."
147
+ echo " Error: $path is not a valid file or directory."
140
148
exit 1
141
149
fi
142
150
143
- # If any file required normalization in --check mode, exit non-zero
144
- if [ $status -ne 0 ]; then
145
- exit 1
146
- fi
151
+ # If in --check mode, and we found at least one file needing changes, exit 1
152
+ exit " $status "
147
153
}
148
154
149
155
main " $@ "
0 commit comments