@@ -43,6 +43,30 @@ print_usage_and_efi_data () {
43
43
efibootmgr --verbose
44
44
}
45
45
46
+ # function to print debug messages if test_mode is enabled
47
+ # usage: debug "your message here"
48
+ debug () {
49
+ if [[ $test_mode -eq 1 ]]; then
50
+ echo " DEBUG: $* " >&2
51
+ fi
52
+ }
53
+
54
+ # The reason we are doing is because it seems that efibootmgr output differ slightly across different distros.
55
+ # Arch and Fedora do not have the `File` prefix in the loader path, while Ubuntu and Mint does.
56
+
57
+ # standard efibootmgr (most common): ends with loader path
58
+ REGEX_LOADER=' ^Boot([[:xdigit:]]{4})\*?[[:blank:]]+(.+)[[:blank:]]+HD\(([[:digit:]]+),[^,]+,([^,]+)[^\)]+\)\/(.+)$'
59
+
60
+ # alternative format: loader path wrapped in File(...)
61
+ REGEX_LOADER_FILE=' ^Boot([[:xdigit:]]{4})\*?[[:blank:]]+(.+)[[:blank:]]+HD\(([[:digit:]]+),[^,]+,([^,]+)[^\)]+\)\/File\(([^\)]+)\)$'
62
+
63
+ # sfdisk format for extracting device and partition uuid
64
+ # e.g. /dev/sda1 : start=..., size=..., ..., uuid=2FFCC127-F6CE-40F0-9932-D1DFD14E9462
65
+ REGEX_SFDISK_UUID=' ^([^[:blank:]]+)[[:blank:]]:[[:blank:]].*[[:blank:]]uuid=([^,]+)'
66
+
67
+ REGEX_UUID=' ^(/dev/(sd[a-z]|nvme[[:digit:]]+n[[:digit:]]+|mmcblk[[:digit:]]+))p?([[:digit:]]+)$'
68
+ # ----------------------------------------------------------------------------------------------------------------------
69
+
46
70
# script start ---------------------------------------------------------------------------------------------------------
47
71
48
72
# check whether we run as root
@@ -68,31 +92,50 @@ old_label="$1"
68
92
new_label=" $2 "
69
93
old_bootnum=" $3 "
70
94
95
+ # default: execute normally
96
+ test_mode=0
97
+
98
+ # parse remaining arguments for optional flags
99
+ for arg in " $@ " ; do
100
+ if [[ " $arg " == " --test" ]]; then
101
+ test_mode=1
102
+ fi
103
+ done
104
+
105
+ if [[ $test_mode -eq 1 ]]; then
106
+ echo " $0 : INFO : test mode enabled; no commands will be executed, only verification and dry run"
107
+ fi
108
+
109
+
71
110
# obtain disk device names as `disk_names` array -----------------------------------------------------------------------
72
111
73
112
# obtain disk data as long text
74
113
disk_data_all=$( lsblk --nodeps --noheadings --pairs | grep ' TYPE="disk"' )
75
114
76
115
# split disk data into a string array
116
+ debug " Finding disk devices..."
77
117
readarray -t disk_data_array <<< " $disk_data_all"
78
118
79
119
# obtain an array of disk names
80
120
disk_names=()
81
121
for disk_data_line in " ${disk_data_array[@]} " ; do
82
122
if [[ $disk_data_line =~ ^NAME= \" ([^\" ]+)\" ]] ; then
83
123
disk_names+=(${BASH_REMATCH[1]} )
124
+ debug " Found disk: ${BASH_REMATCH[1]} "
84
125
fi
85
126
done
86
127
# ----------------------------------------------------------------------------------------------------------------------
87
128
88
129
# obtain an associative array of devices against partition uuid values -------------------------------------------------
130
+ debug " Mapping partition UUIDs to device names..."
89
131
90
132
# the associative array to be filled
91
133
# data example : partitions['2ffcc127-f6ce-40f0-9932-d1dfd14e9462']='/dev/sda1'
92
134
declare -A partitions
93
135
94
136
# loop over all disk devices
95
137
for disk_name in " ${disk_names[@]} " ; do
138
+ debug " Scanning disk /dev/$disk_name for partitions..."
96
139
# obtain partition data as long text, suppressing possible error messages in stderr,
97
140
# e.g. "sfdisk: /dev/sdb: does not contain a recognized partition table"
98
141
# NOTE sfdisk call requires `sudo`
@@ -107,42 +150,78 @@ for disk_name in "${disk_names[@]}" ; do
107
150
# /dev/sda1 : start= 2048, size= 1048576, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=2FFCC127-F6CE-40F0-9932-D1DFD14E9462, name="EFI System Partition"
108
151
# /dev/sda1 : start= 2048, size= 1048576, type=C12A7328-F81F-11D2-BA4B-00A0C93EC93B, uuid=2FFCC127-F6CE-40F0-9932-D1DFD14E9462
109
152
for partition_data_line in " ${partition_data_array[@]} " ; do
110
- if [[ $partition_data_line =~ ^([^[:blank:]]+)[[:blank:]]:[[:blank:]]. * [[:blank:]]uuid = ([^,]+) ]] ; then
153
+ if [[ $partition_data_line =~ $REGEX_SFDISK_UUID ]] ; then
111
154
device=${BASH_REMATCH[1]}
112
155
uuid_lowercase=" ${BASH_REMATCH[2],,} "
113
156
partitions[$uuid_lowercase ]=$device
157
+ debug " Mapped UUID $uuid_lowercase to $device "
158
+
114
159
fi
115
160
done
116
161
done
162
+ if [ ${# partitions[@]} -eq 0 ]; then
163
+ echo " $0 : WARNING : Could not find any partitions with UUIDs via sfdisk."
164
+ fi
117
165
# ----------------------------------------------------------------------------------------------------------------------
118
166
119
167
# obtain EFI data ------------------------------------------------------------------------------------------------------
168
+ debug " Scanning EFI boot entries..."
120
169
121
170
# obtain EFI data as long text
122
171
efi_data_all=$( efibootmgr --verbose)
123
172
124
173
# split EFI data into a string array
125
174
readarray -t efi_data_array <<< " $efi_data_all"
126
175
176
+ target_bootnum=" "
177
+ target_part=" "
178
+ target_uuid=" "
179
+ target_loader=" "
180
+
127
181
# obtain EFI data for a matching label
128
182
for efi_data_line in " ${efi_data_array[@]} " ; do
129
- if [[ $efi_data_line =~ ^Boot([[:xdigit:]]{4})\* ? [[:blank:]]+ (.+)[[:blank:]]+HD\( ([[:digit:]]+),[^,]+,([^,]+)[^\) ]+\) /File\( ([^\) ]+)\) ]] ; then
130
- label=" ${BASH_REMATCH[2]} "
131
- if [ " $label " = " $old_label " ] || [ " $old_label " = ' *' ] ; then
132
- if [ -z " $target_bootnum " ] ; then # no `bootnum` match or candidate found yet
133
- if [ -z " $old_bootnum " ] || [ ${BASH_REMATCH[1]} = " $old_bootnum " ] ; then
134
- target_bootnum=${BASH_REMATCH[1]}
135
- target_part=${BASH_REMATCH[3]}
136
- target_uuid=${BASH_REMATCH[4]}
137
- target_loader=${BASH_REMATCH[5]}
138
- fi
139
- else # `bootnum` match or candidate already found
140
- if [ -z " $old_bootnum " ] ; then
141
- echo " ERROR: more than one boot entry found with label matching '$old_label ': $target_bootnum and ${BASH_REMATCH[1]} ;"
142
- echo " please use optional 'bootnum' command line argument to resolve this ambiguity."
143
- exit 1
144
- fi
183
+
184
+ # These will be set if a line matches one of our regexes
185
+ line_bootnum=" " line_label=" " line_part=" " line_uuid=" " line_loader=" "
186
+
187
+ # Try to match the line against known formats
188
+ if [[ $efi_data_line =~ $REGEX_LOADER ]]; then
189
+ line_bootnum=" ${BASH_REMATCH[1]} "
190
+ line_label=" ${BASH_REMATCH[2]} "
191
+ line_part=" ${BASH_REMATCH[3]} "
192
+ line_uuid=" ${BASH_REMATCH[4],,} " # lowercase for consistency
193
+ line_loader=" ${BASH_REMATCH[5]} "
194
+ debug " Parsed entry: BootNum=$line_bootnum , Label='$line_label ' (standard format)"
195
+
196
+ elif [[ $efi_data_line =~ $REGEX_LOADER_FILE ]]; then
197
+ line_bootnum=" ${BASH_REMATCH[1]} "
198
+ line_label=" ${BASH_REMATCH[2]} "
199
+ line_part=" ${BASH_REMATCH[3]} "
200
+ line_uuid=" ${BASH_REMATCH[4],,} " # lowercase for consistency
201
+ line_loader=" ${BASH_REMATCH[5]} "
202
+ debug " Parsed entry: BootNum=$line_bootnum , Label='$line_label ' (File() format)"
203
+
204
+ else
205
+ debug " Line did not match any known EFI entry format, skipping: $efi_data_line "
206
+ continue
207
+ fi
208
+
209
+ # Now check if this parsed line is the one we are looking for
210
+ if [[ " $old_label " == " $line_label " || " $old_label " == " *" ]]; then
211
+ # Label matches. Now check if boot number matches (if one was provided).
212
+ if [[ -z " $old_bootnum " || " $old_bootnum " == " $line_bootnum " ]]; then
213
+ # This is a match. Check for ambiguity.
214
+ if [[ -n " $target_bootnum " ]]; then
215
+ echo " $0 : ERROR : more than one boot entry found matching label '$old_label '." >&2
216
+ echo " Found $target_bootnum and now $line_bootnum . Please use the optional 'bootnum' argument to specify which one." >&2
217
+ exit 1
145
218
fi
219
+ # This is our first and only match so far. Store its details.
220
+ debug " Found a matching target entry: Boot$line_bootnum "
221
+ target_bootnum=" $line_bootnum "
222
+ target_part=" $line_part "
223
+ target_uuid=" $line_uuid "
224
+ target_loader=" $line_loader "
146
225
fi
147
226
fi
148
227
done
@@ -153,21 +232,23 @@ if [ -z "$target_bootnum" ] ; then
153
232
echo " $0 : ERROR : no EFI data found for any label matching '$old_label '."
154
233
exit 1
155
234
fi
235
+ debug " Target found: BootNum=$target_bootnum , Part=$target_part , UUID=$target_uuid , Loader=$target_loader "
156
236
157
237
# obtain device for the partition with uuid that corresponds to the given label
158
238
device_for_uuid=${partitions[$target_uuid]}
159
239
if [ -z " $device_for_uuid " ] ; then
160
240
echo " $0 : ERROR : EFI label '$old_label ' is related to partition '$target_uuid ' that is not currently known to the system."
161
241
exit 1
162
242
fi
243
+ debug " Partition UUID $target_uuid corresponds to device $device_for_uuid "
163
244
164
245
# verify that device/partition name matches some expected pattern;
165
246
# the following partition name patterns/samples are recognized:
166
247
# - SCSI family : e.g. /dev/sda1
167
248
# - NVMe : e.g. /dev/nvme0n1p1
168
249
# - MMC family : e.g. /dev/mmcblk0p1
169
250
# see https://wiki.archlinux.org/index.php/Device_file#Block_device_names
170
- if [[ $device_for_uuid =~ ^(/dev/(sd[a-z] | nvme[[:digit:]]+n[[:digit:]]+ | mmcblk[[:digit:]]+))p ? ([[:digit:]]+)$ ]] ; then
251
+ if [[ $device_for_uuid =~ $REGEX_UUID ]] ; then
171
252
device_name=${BASH_REMATCH[1]}
172
253
device_part=${BASH_REMATCH[3]}
173
254
else
@@ -176,27 +257,36 @@ else
176
257
fi
177
258
178
259
# verify that partition number of the device matches partition number in the EFI entry
179
- if [[ $device_part != $target_part ]] ; then
260
+ if [[ $device_part != " $target_part " ]] ; then
180
261
echo " $0 : ERROR : partition number of the device [$device_part ] is different from partition number in the EFI entry [$target_part ]."
181
262
exit 1
182
263
fi
183
264
184
265
# prepare efibootmgr commands to be executed
185
- printf -v escaped_loader " %q" $target_loader
266
+ printf -v escaped_loader " %q" " $target_loader "
186
267
efi_command_1=" efibootmgr --bootnum $target_bootnum --delete-bootnum"
187
268
efi_command_2=" efibootmgr --create --disk $device_name --part $target_part --label '$new_label ' --loader $escaped_loader "
188
269
189
270
# obtain final user consent and apply modifications
190
271
echo The following commands are about to be executed:
191
272
echo " $efi_command_1 "
192
273
echo " $efi_command_2 "
193
- read -p " Execute these commands? [y/N] " -n 1 -r
194
- echo # terminate the shell UI line
195
- if [[ $REPLY =~ ^[Yy]$ ]] ; then
196
- echo " ... executing \` $efi_command_1 \` ..."
197
- eval $efi_command_1
198
- echo " ... executing \` $efi_command_2 \` ..."
199
- eval $efi_command_2
274
+
275
+ if [[ $test_mode -eq 1 ]]; then
276
+ echo " $0 : INFO : --test mode enabled. No changes will be made."
277
+ echo " $0 : INFO : Script validation complete. Ready to rename EFI entry."
200
278
else
201
- echo " $0 : INFO : command execution aborted"
202
- fi
279
+ read -p " Execute these commands? [y/N] " -n 1 -r
280
+ echo # terminate the shell UI line
281
+ if [[ $REPLY =~ ^[Yy]$ ]] ; then
282
+ echo " ... executing \` $efi_command_1 \` ..."
283
+ eval " $efi_command_1 "
284
+ echo " ... executing \` $efi_command_2 \` ..."
285
+ eval " $efi_command_2 "
286
+ echo
287
+ echo " Done. Verifying new entry:"
288
+ efibootmgr | grep --color=always " $new_label "
289
+ else
290
+ echo " $0 : INFO : Command execution aborted by user."
291
+ fi
292
+ fi
0 commit comments