├── screenshots ├── menu.png └── submenu.png ├── scripts └── generate-manpage.sh ├── README.md ├── manpage.pod ├── bashmount.conf ├── bashmount.1 ├── NEWS ├── COPYING └── bashmount /screenshots/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamielinux/bashmount/HEAD/screenshots/menu.png -------------------------------------------------------------------------------- /screenshots/submenu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamielinux/bashmount/HEAD/screenshots/submenu.png -------------------------------------------------------------------------------- /scripts/generate-manpage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | read -r -e -p "version: " version 3 | cat ./manpage.pod \ 4 | | pod2man --name BASHMOUNT --release "bashmount $version" --center bashmount \ 5 | > bashmount.1 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bashmount 2 | 3 | `bashmount` is a command-line tool to easily manage removable media. 4 | 5 | ![bashmount screenshot](./screenshots/menu.png) 6 | ![bashmount submenu screenshot](./screenshots/submenu.png) 7 | 8 | ## Installation 9 | 10 | 1. Install the 11 | [script](https://raw.githubusercontent.com/jamielinux/bashmount/master/bashmount) 12 | somewhere in your path (eg, `/usr/bin/bashmount`). 13 | 2. _Optional:_ Install the 14 | [configuration file](https://raw.githubusercontent.com/jamielinux/bashmount/master/bashmount.conf) 15 | at either `/etc/bashmount.conf` or `$HOME/.config/bashmount/config`. 16 | 17 | There are also packages available for: 18 | 19 | - [Fedora](https://src.fedoraproject.org/rpms/bashmount) 20 | - [Arch Linux](https://aur.archlinux.org/packages/bashmount/) 21 | - [Gentoo](https://packages.gentoo.org/packages/sys-fs/bashmount) 22 | 23 | Add something like this to your `$HOME/.bashrc` to make life easier: 24 | 25 | ``` 26 | alias bm='bashmount' 27 | ``` 28 | 29 | ### Dependencies 30 | 31 | * `bash` 32 | * `eject` 33 | * _Optional:_ `udisks2` 34 | 35 | `udisks2` is recommended, so that regular users can manage removable media 36 | without sudo. By default, `udisks2` is auto-detected, but you can force use 37 | of `udisks2` on or off in the configuration file. 38 | 39 | ## Configuration 40 | 41 | `bashmount` works fine without a configuration file, but there's some neat 42 | things you can do. 43 | 44 | Have a look at the example 45 | [configuration file](https://github.com/jamielinux/bashmount/blob/master/bashmount.conf). 46 | 47 | - Open your desired file manager at the mountpoint of a device. (The default is 48 | to open in your terminal.) 49 | - Hide certain devices from the list. 50 | - Configure custom commands to run on removable devices. 51 | - Automatically run custom commands after a mount or unmount. 52 | -------------------------------------------------------------------------------- /manpage.pod: -------------------------------------------------------------------------------- 1 | =head1 NAME 2 | 3 | bashmount - tool to mount and unmount removable media 4 | 5 | =head1 SYNOPSIS 6 | 7 | I 8 | 9 | =head1 DESCRIPTION 10 | 11 | bashmount is a menu-driven bash script that can use different backends to 12 | easily mount, unmount or eject removable devices without dependencies on any 13 | GUI. An extensive configuration file allows many aspects of the script to be 14 | modified and custom commands to be run on devices. 15 | 16 | bashmount can also be used alongside any automounting solutions, such as 17 | graphical file managers, udev rules or Udiskie. 18 | 19 | =head1 FILES 20 | 21 | =over 4 22 | 23 | =item B 24 | 25 | Default configuration file. An example configuration file can be seen at 26 | L. 27 | 28 | =item B<$XDG_CONFIG_HOME/bashmount/config> 29 | 30 | User-specific configuration file. 31 | 32 | =back 33 | 34 | =head1 BUGS 35 | 36 | If you find any bugs, please send an email to the author, or create a new 37 | issue at L. 38 | 39 | =head1 AUTHORS 40 | 41 | Jamie Nguyen Ej@jamielinux.comE 42 | 43 | Lukas B. 44 | 45 | =head1 COPYRIGHT 46 | 47 | Copyright (C) 2013-2020 Jamie Nguyen 48 | Copyright (C) 2014 Lukas B. 49 | 50 | This program is free software; you can redistribute it and/or modify it 51 | under the terms of the GNU General Public License v2 as published by the 52 | Free Software Foundation. 53 | 54 | This program is distributed in the hope that it will be useful, but WITHOUT 55 | ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 56 | FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 57 | more details. 58 | 59 | You should have received a copy of the GNU General Public License along with 60 | this program; if not, write to the Free Software Foundation, Inc., 61 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 62 | -------------------------------------------------------------------------------- /bashmount.conf: -------------------------------------------------------------------------------- 1 | # 2 | # bashmount.conf 3 | # 4 | 5 | # Place in /etc/bashmount.conf for system wide configuration. 6 | # Place in ~/.config/bashmount/config for per-user configuration. 7 | # XDG_CONFIG_HOME is respected if it has been set. 8 | 9 | # Set whether to or not to use udisksctl. The default is to autodetect whether 10 | # it is available. This can be '0', '1', or 'auto'. 11 | #udisks='auto' 12 | 13 | # Set which sections to show in the output. These can be '0' or '1'. 14 | #show_internal='1' 15 | #show_removable='1' 16 | #show_optical='1' 17 | #show_commands='1' 18 | 19 | # Set whether to colourize the output. This can be '0' or '1'. 20 | #colourize='1' 21 | 22 | # Set whether to use unicode for pretty layout. This can be '0' or '1'. 23 | #pretty='1' 24 | 25 | # Set default mount options. The default is to pass no options. 26 | #mount_options='--options nosuid,noexec,noatime' 27 | 28 | # Set devices to exclude. Any device whose "lsblk -P" output contains a string 29 | # listed here will be hidden. The following key-value-pairs are printed: 30 | # lsblk -dPno NAME,TYPE,FSTYPE,LABEL,MOUNTPOINT,PARTLABEL,UUID [device_name] 31 | # The strings are matched using "grep -E" regular expression. 32 | #exclude=() 33 | 34 | ### 35 | ### This example will match any device with "Photosmart" in any field. 36 | ### exclude=( 'Photosmart' ) 37 | ### 38 | ### This example will match the device found at /dev/sda1. 39 | ### exclude=( 'NAME="sda1"' ) 40 | ### 41 | ### This example will match /dev/sda1, /dev/sda2 and /dev/sda3. 42 | ### exclude=( 'NAME="sda[1-3]"' ) 43 | ### 44 | ### This example matches several different devices. Note that the array can be 45 | ### split over multiple lines for convenience. 46 | ### exclude=( 'FSTYPE="ext3"' ) 47 | ### exclude+=( 'LABEL="secret"' ) 48 | ### exclude+=( 'MOUNTPOINT="/"' ) 49 | ### exclude+=( 'UUID="0c4d6d9c-87a2-4579-b2ae-35b0790c718a"' ) 50 | ### 51 | 52 | # Set filemanager command to use when performing the "open" command. The mount 53 | # point is passed as the first argument. The default is to open the directory 54 | # within your terminal. 55 | 56 | # Uncomment this example to launch a graphical file manager. 57 | #filemanager() { 58 | # /usr/bin/nautilus "$1" & >/dev/null 2>&1 59 | #} 60 | 61 | # Uncomment this example to launch midnight commander in a new window. 62 | #filemanager() { 63 | # /usr/bin/uxterm -wf -e /usr/bin/mc "$1" & >/dev/null 2>&1 64 | #} 65 | 66 | # Set custom commands. Set "customX_show" to '0' to disable or '1' to enable. 67 | # If enabled, the custom commands will be shown in the device sub-menu. The 68 | # description and command functions can be set to anything you desire. The 69 | # device name (e.g. /dev/sdb1) is passed as the first and only argument. 70 | # A maximum of 3 custom actions are configurable. 71 | 72 | #custom4_show='0' 73 | #custom4_desc='changeme' 74 | #custom4_command() { 75 | # sudo mount --options remount,ro "$1" "/mnt/${1##*/}" 76 | #} 77 | 78 | #custom5_show='0' 79 | #custom5_desc='changeme' 80 | #custom5_command() { 81 | # 82 | #} 83 | 84 | #custom6_show='0' 85 | #custom6_desc='changeme' 86 | #custom6_command() { 87 | # 88 | #} 89 | 90 | # Set custom commands to be run automatically after mounting or unmounting. 91 | # post_mount is run immediately after a successful mount operation, and 92 | # post_unmount is run immediately after a successful unmount operation. 93 | 94 | # Set "run_post_mount" to '0' to disable or '1' to enable. 95 | #run_post_mount='0' 96 | 97 | #post_mount() { 98 | # 99 | #} 100 | 101 | # Set "run_post_unmount" to '0' to disable or '1' to enable. 102 | #run_post_unmount='0' 103 | 104 | #post_unmount() { 105 | # 106 | #} 107 | -------------------------------------------------------------------------------- /bashmount.1: -------------------------------------------------------------------------------- 1 | .\" Automatically generated by Pod::Man 4.14 (Pod::Simple 3.40) 2 | .\" 3 | .\" Standard preamble: 4 | .\" ======================================================================== 5 | .de Sp \" Vertical space (when we can't use .PP) 6 | .if t .sp .5v 7 | .if n .sp 8 | .. 9 | .de Vb \" Begin verbatim text 10 | .ft CW 11 | .nf 12 | .ne \\$1 13 | .. 14 | .de Ve \" End verbatim text 15 | .ft R 16 | .fi 17 | .. 18 | .\" Set up some character translations and predefined strings. \*(-- will 19 | .\" give an unbreakable dash, \*(PI will give pi, \*(L" will give a left 20 | .\" double quote, and \*(R" will give a right double quote. \*(C+ will 21 | .\" give a nicer C++. Capital omega is used to do unbreakable dashes and 22 | .\" therefore won't be available. \*(C` and \*(C' expand to `' in nroff, 23 | .\" nothing in troff, for use with C<>. 24 | .tr \(*W- 25 | .ds C+ C\v'-.1v'\h'-1p'\s-2+\h'-1p'+\s0\v'.1v'\h'-1p' 26 | .ie n \{\ 27 | . ds -- \(*W- 28 | . ds PI pi 29 | . if (\n(.H=4u)&(1m=24u) .ds -- \(*W\h'-12u'\(*W\h'-12u'-\" diablo 10 pitch 30 | . if (\n(.H=4u)&(1m=20u) .ds -- \(*W\h'-12u'\(*W\h'-8u'-\" diablo 12 pitch 31 | . ds L" "" 32 | . ds R" "" 33 | . ds C` "" 34 | . ds C' "" 35 | 'br\} 36 | .el\{\ 37 | . ds -- \|\(em\| 38 | . ds PI \(*p 39 | . ds L" `` 40 | . ds R" '' 41 | . ds C` 42 | . ds C' 43 | 'br\} 44 | .\" 45 | .\" Escape single quotes in literal strings from groff's Unicode transform. 46 | .ie \n(.g .ds Aq \(aq 47 | .el .ds Aq ' 48 | .\" 49 | .\" If the F register is >0, we'll generate index entries on stderr for 50 | .\" titles (.TH), headers (.SH), subsections (.SS), items (.Ip), and index 51 | .\" entries marked with X<> in POD. Of course, you'll have to process the 52 | .\" output yourself in some meaningful fashion. 53 | .\" 54 | .\" Avoid warning from groff about undefined register 'F'. 55 | .de IX 56 | .. 57 | .nr rF 0 58 | .if \n(.g .if rF .nr rF 1 59 | .if (\n(rF:(\n(.g==0)) \{\ 60 | . if \nF \{\ 61 | . de IX 62 | . tm Index:\\$1\t\\n%\t"\\$2" 63 | .. 64 | . if !\nF==2 \{\ 65 | . nr % 0 66 | . nr F 2 67 | . \} 68 | . \} 69 | .\} 70 | .rr rF 71 | .\" 72 | .\" Accent mark definitions (@(#)ms.acc 1.5 88/02/08 SMI; from UCB 4.2). 73 | .\" Fear. Run. Save yourself. No user-serviceable parts. 74 | . \" fudge factors for nroff and troff 75 | .if n \{\ 76 | . ds #H 0 77 | . ds #V .8m 78 | . ds #F .3m 79 | . ds #[ \f1 80 | . ds #] \fP 81 | .\} 82 | .if t \{\ 83 | . ds #H ((1u-(\\\\n(.fu%2u))*.13m) 84 | . ds #V .6m 85 | . ds #F 0 86 | . ds #[ \& 87 | . ds #] \& 88 | .\} 89 | . \" simple accents for nroff and troff 90 | .if n \{\ 91 | . ds ' \& 92 | . ds ` \& 93 | . ds ^ \& 94 | . ds , \& 95 | . ds ~ ~ 96 | . ds / 97 | .\} 98 | .if t \{\ 99 | . ds ' \\k:\h'-(\\n(.wu*8/10-\*(#H)'\'\h"|\\n:u" 100 | . ds ` \\k:\h'-(\\n(.wu*8/10-\*(#H)'\`\h'|\\n:u' 101 | . ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'^\h'|\\n:u' 102 | . ds , \\k:\h'-(\\n(.wu*8/10)',\h'|\\n:u' 103 | . ds ~ \\k:\h'-(\\n(.wu-\*(#H-.1m)'~\h'|\\n:u' 104 | . ds / \\k:\h'-(\\n(.wu*8/10-\*(#H)'\z\(sl\h'|\\n:u' 105 | .\} 106 | . \" troff and (daisy-wheel) nroff accents 107 | .ds : \\k:\h'-(\\n(.wu*8/10-\*(#H+.1m+\*(#F)'\v'-\*(#V'\z.\h'.2m+\*(#F'.\h'|\\n:u'\v'\*(#V' 108 | .ds 8 \h'\*(#H'\(*b\h'-\*(#H' 109 | .ds o \\k:\h'-(\\n(.wu+\w'\(de'u-\*(#H)/2u'\v'-.3n'\*(#[\z\(de\v'.3n'\h'|\\n:u'\*(#] 110 | .ds d- \h'\*(#H'\(pd\h'-\w'~'u'\v'-.25m'\f2\(hy\fP\v'.25m'\h'-\*(#H' 111 | .ds D- D\\k:\h'-\w'D'u'\v'-.11m'\z\(hy\v'.11m'\h'|\\n:u' 112 | .ds th \*(#[\v'.3m'\s+1I\s-1\v'-.3m'\h'-(\w'I'u*2/3)'\s-1o\s+1\*(#] 113 | .ds Th \*(#[\s+2I\s-2\h'-\w'I'u*3/5'\v'-.3m'o\v'.3m'\*(#] 114 | .ds ae a\h'-(\w'a'u*4/10)'e 115 | .ds Ae A\h'-(\w'A'u*4/10)'E 116 | . \" corrections for vroff 117 | .if v .ds ~ \\k:\h'-(\\n(.wu*9/10-\*(#H)'\s-2\u~\d\s+2\h'|\\n:u' 118 | .if v .ds ^ \\k:\h'-(\\n(.wu*10/11-\*(#H)'\v'-.4m'^\v'.4m'\h'|\\n:u' 119 | . \" for low resolution devices (crt and lpr) 120 | .if \n(.H>23 .if \n(.V>19 \ 121 | \{\ 122 | . ds : e 123 | . ds 8 ss 124 | . ds o a 125 | . ds d- d\h'-1'\(ga 126 | . ds D- D\h'-1'\(hy 127 | . ds th \o'bp' 128 | . ds Th \o'LP' 129 | . ds ae ae 130 | . ds Ae AE 131 | .\} 132 | .rm #[ #] #H #V #F C 133 | .\" ======================================================================== 134 | .\" 135 | .IX Title "BASHMOUNT 1" 136 | .TH BASHMOUNT 1 "2020-09-08" "bashmount 4.3.2" "bashmount" 137 | .\" For nroff, turn off justification. Always turn off hyphenation; it makes 138 | .\" way too many mistakes in technical documents. 139 | .if n .ad l 140 | .nh 141 | .SH "NAME" 142 | bashmount \- tool to mount and unmount removable media 143 | .SH "SYNOPSIS" 144 | .IX Header "SYNOPSIS" 145 | \&\fIbashmount\fR 146 | .SH "DESCRIPTION" 147 | .IX Header "DESCRIPTION" 148 | bashmount is a menu-driven bash script that can use different backends to 149 | easily mount, unmount or eject removable devices without dependencies on any 150 | \&\s-1GUI.\s0 An extensive configuration file allows many aspects of the script to be 151 | modified and custom commands to be run on devices. 152 | .PP 153 | bashmount can also be used alongside any automounting solutions, such as 154 | graphical file managers, udev rules or Udiskie. 155 | .SH "FILES" 156 | .IX Header "FILES" 157 | .IP "\fB/etc/bashmount.conf\fR" 4 158 | .IX Item "/etc/bashmount.conf" 159 | Default configuration file. An example configuration file can be seen at 160 | . 161 | .IP "\fB\f(CB$XDG_CONFIG_HOME\fB/bashmount/config\fR" 4 162 | .IX Item "$XDG_CONFIG_HOME/bashmount/config" 163 | User-specific configuration file. 164 | .SH "BUGS" 165 | .IX Header "BUGS" 166 | If you find any bugs, please send an email to the author, or create a new 167 | issue at . 168 | .SH "AUTHORS" 169 | .IX Header "AUTHORS" 170 | Jamie Nguyen 171 | .PP 172 | Lukas B. 173 | .SH "COPYRIGHT" 174 | .IX Header "COPYRIGHT" 175 | .Vb 2 176 | \& Copyright (C) 2013\-2020 Jamie Nguyen 177 | \& Copyright (C) 2014 Lukas B. 178 | .Ve 179 | .PP 180 | This program is free software; you can redistribute it and/or modify it 181 | under the terms of the \s-1GNU\s0 General Public License v2 as published by the 182 | Free Software Foundation. 183 | .PP 184 | This program is distributed in the hope that it will be useful, but \s-1WITHOUT 185 | ANY WARRANTY\s0; without even the implied warranty of \s-1MERCHANTABILITY\s0 or 186 | \&\s-1FITNESS FOR A PARTICULAR PURPOSE.\s0 See the \s-1GNU\s0 General Public License for 187 | more details. 188 | .PP 189 | You should have received a copy of the \s-1GNU\s0 General Public License along with 190 | this program; if not, write to the Free Software Foundation, Inc., 191 | 51 Franklin Street, Fifth Floor, Boston, \s-1MA\s0 02110\-1301, \s-1USA\s0 192 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | bashmount 4.3.2 (2020-09-08) 2 | - Fix colourize=0 3 | 4 | bashmount 4.3.1 (2020-08-22) 5 | - Use sed s/ instead of y/ as the strings are different lengths 6 | 7 | bashmount 4.3.0 (2020-07-02) 8 | - Fix bug when opening crypto_LUKS devices directly 9 | - Fix error message when an unmount fails 10 | - Fix bug that inadvertently hid disks with no partitions 11 | 12 | bashmount 4.2.5 (2020-06-12) 13 | - Tweak appearance 14 | 15 | bashmount 4.2.4 (2020-06-12) 16 | - Tweak appearance 17 | 18 | bashmount 4.2.3 (2020-06-12) 19 | - Run non-udisks commands with sudo 20 | 21 | bashmount 4.2.2 (2020-06-12) 22 | - Fix handling of non-udisks mounting 23 | 24 | bashmount 4.2.1 (2020-06-12) 25 | - Improve display of custom commands 26 | 27 | bashmount 4.2.0 (2020-06-12) 28 | - Allow to mount/unmount/eject/open directly on crypto_LUKS devices 29 | - Rename default_mount_options to mount_options 30 | - Remove show_full_device_names option 31 | - Remove ability to override mount/unmount commands 32 | - More code cleanup 33 | 34 | Thanks to nick87720z (aka https://github.com/nick87720z) for: 35 | - https://github.com/jamielinux/bashmount/pull/21 36 | - Use nicer unicode characters for some UI elements, configurable with 37 | the 'pretty' option. 38 | 39 | Thanks to Jonatan Olofsson (aka https://github.com/jonatanolofsson) for: 40 | - https://github.com/jamielinux/bashmount/pull/22 41 | - Treat Ctrl-D the same as 'q' (ie, quit). 42 | - https://github.com/jamielinux/bashmount/pull/23 43 | - Put default filemanager inside subshell to avoid lock on unmount 44 | 45 | bashmount 4.1.0 (2020-06-11) 46 | - Only require one key press in device submenu 47 | 48 | bashmount 4.0.1 (2020-06-11) 49 | - Improve formatting 50 | - Fix help text 51 | 52 | bashmount 4.0.0 (2020-06-11) 53 | - Cleanup code, improve style and readability 54 | - Fix some typos 55 | - Improve some of the error messages 56 | - Make the UI wider (now 77 characters) to accommodate more information 57 | - Display device size and mountpoint in main menu 58 | - Improve column alignment 59 | - Run `udisksctl --power-off` when ejecting, if udisksctl is available 60 | - Tweak the default filemanager ($SHELL) to not exit afterwards 61 | - Replace \x20 in device labels with a space character 62 | - Handle LUKS devices better 63 | - Change the separator from = to - for a lighter appearance 64 | - Reduce times you have to press [enter] to continue (eg, skip if the 65 | command was successful) 66 | - Pressing [r] and [enter] refreshes the device list, but just pressing 67 | [enter] by itself has always worked too. Now the help text prefers 68 | [enter] to make it discoverable and reduce key presses. 69 | 70 | bashmount 3.2.0 (2014-04-19) 71 | - Shorten long device names and add padding so that device labels all 72 | align vertically, making things easier to read 73 | - Add show_full_device_names option to bashmount.conf 74 | 75 | bashmount 3.1.1 (2014-04-04) 76 | Thanks to Lukas B. (aka https://github.com/vitamins) for these changes: 77 | - Avoid forked grep process 78 | 79 | bashmount 3.1.0 (2014-04-04) 80 | - Add logic to display disks with no partitions (eg, internal storage on 81 | some portable media devices) 82 | 83 | bashmount 3.0.3 (2014-03-24) 84 | Thanks to Lukas B. (aka https://github.com/vitamins) for these changes: 85 | - Save UI space by removing bashmount header 86 | - Add `--version` command-line option 87 | 88 | bashmount 3.0.2 (2014-03-23) 89 | Thanks to Lukas B. (aka https://github.com/vitamins) for these changes: 90 | - Fix newline in UI (again) 91 | - Use new syntax for cryptsetup 92 | 93 | bashmount 3.0.1 (2014-03-23) 94 | - Default to 'noatime' instead of 'relatime' in the default mount options 95 | 96 | Thanks to Lukas B. (aka https://github.com/vitamins) for these changes: 97 | - When udisks=1 and udisksctl is not available, fail with a message 98 | - Fix handling of read-only mounting 99 | 100 | bashmount 3.0.0 (2014-03-23) 101 | - Allow pressing [enter] to refresh device list 102 | 103 | bashmount 3.0.0beta2 (2014-03-23) 104 | - Fix newline in UI 105 | 106 | bashmount 3.0.0beta1 (2014-03-23) 107 | - Autodetect the presence of udisksctl, but also allow to manually disable 108 | the use of udisks 109 | - Re-order the configuration file and remove redundant options 110 | - Allow blacklisting by TYPE 111 | - The first three custom commands are now built-in to bashmount, so only 112 | custom4_command, custom5_command and custom6_command are configurable 113 | - Remove show_device_path option, and instead always show the device path 114 | 115 | bashmount 3.0.0alpha1 (2014-03-23) 116 | Many thanks to Lukas B. (aka https://github.com/vitamins) for porting 117 | bashmount to use lsblk where appropriate and a variety of other changes: 118 | 119 | - Information retrieval is now based on `lsblk` instead of `udisksctl`, 120 | which allows us to get rid of a lot of horrible parsing 121 | - Devices are sorted by internal media, removable media and optical media 122 | - Only partitions are listed, no disks 123 | - Opened luks containers are also listed by default 124 | 125 | Configuration file: 126 | - Add "mount_command" and "unmount_command" which can be configured by the 127 | user 128 | - "optical_devices" and "removable_devices" are no longer present 129 | - "show_internal" is set to '1' by default 130 | - "show_removable_device_filename" and "show_optical_device_filename" have 131 | been replaced by a single variable named "show_device_path" 132 | - "fancy_sort" is no longer present 133 | 134 | bashmount 2.0.0alpha1 (2013-08-31) 135 | 136 | - Port to udisks2 137 | - New configuration file 138 | - New dependency on `eject` 139 | 140 | bashmount 1.6.2 (2012-02-06) 141 | 142 | - Add some comments 143 | - Add AUTHORS, INSTALL and README.pod files, which obsolete the README 144 | file 145 | - Add NEWS file, which obsoletes the ChangeLog file 146 | - Add man page 147 | - Change default filemanager to the shell when no configuration file can 148 | be found 149 | 150 | bashmount 1.6.1 (2012-02-05) 151 | 152 | - Fix a small bug in print_submenu_commands() 153 | - Keep lines of code more strictly within 80 character width limit 154 | - Change default filemanager to the shell 155 | - Aesthetic improvements to the code 156 | 157 | bashmount 1.6.0 (2011-11-23) 158 | 159 | - Add ability to blacklist devices based on arbitrary string matches 160 | 161 | bashmount 1.5.3 (2011-11-04) 162 | 163 | - Make sure the mount status is up to date in print_submenu_commands() 164 | 165 | bashmount 1.5.2 (2011-11-04) 166 | 167 | - Fix bugs in print_submenu_commands() due to an incorrect variable 168 | declaration and a typo 169 | 170 | bashmount 1.5.1 (2011-09-20) 171 | 172 | - Fix bug in fancy_sort() due to an incorrect variable declaration 173 | 174 | bashmount 1.5.0 (2011-09-16) 175 | 176 | - Refactor code 177 | - Unset GREP_OPTIONS in case the user has it set in their environment 178 | - Move a misplaced printf '\n' 179 | - Fix handling of functions that have not been set in the configuration file 180 | - Remove specification of the udisks command path in the configuration file 181 | - Add some more comments 182 | 183 | bashmount 1.4.1 (2011-09-08) 184 | 185 | - Add declaration of two variables 186 | 187 | bashmount 1.4.0 (2011-09-06) 188 | 189 | - Refactor code 190 | - Add options to run any arbitrary command immediately after a successful 191 | mount or unmount operation, such as commands to spin down a device 192 | - If an operation fails in the device sub-menu, do not return to the top 193 | menu 194 | 195 | bashmount 1.3.1 (2011-05-31) 196 | 197 | - Fix display of optical device number 198 | 199 | bashmount 1.3.0 (2011-05-26) 200 | 201 | - Fix declaration of arrays 202 | - Fix handling of devices with 10 or more partitions 203 | - Add a fancy_sort option in the configuration file for a very hack way to 204 | make sure /dev/sdc11 does not come before /dev/sdc2 in the menu (which 205 | occurs due to a shortcoming of the sort command) 206 | - Use printf instead of echo 207 | 208 | bashmount 1.2.0 (2011-05-23) 209 | 210 | - Refactor code 211 | - Fix "unmount all" command 212 | - Exit correctly if there are errors on sourcing the configuration file 213 | - Various minor aesthetic improvements 214 | 215 | bashmount 1.1.4 (2011-05-19) 216 | 217 | - Refactor code 218 | 219 | bashmount 1.1.3 (2011-05-08) 220 | 221 | - Fix numbering and display of devices 222 | 223 | bashmount 1.1.2 (2011-05-08) 224 | 225 | - Improve configuration file 226 | 227 | bashmount 1.1.1 (2011-05-08) 228 | 229 | - Fix handling of /dev/mmcblk* devices both in the script and in the 230 | configuration file 231 | 232 | bashmount 1.1.0 (2011-05-08) 233 | 234 | - Improve configuration file 235 | - Improve help screen 236 | - Add support for matching /dev/mmcblk* devices such as SD card readers 237 | 238 | bashmount 1.0.0 (2011-04-27) 239 | 240 | - Initial release 241 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | 294 | Copyright (C) 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | , 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /bashmount: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -u 3 | 4 | declare -r VERSION="4.3.2" 5 | 6 | #=============================================================================# 7 | # FILE: bashmount # 8 | # WEBSITE: https://github.com/jamielinux/bashmount # 9 | # DESCRIPTION: bashmount is a menu-driven bash script that can use different # 10 | # backends to easily mount, unmount or eject removable devices # 11 | # without dependencies on any GUI. An extensive configuration # 12 | # file allows many aspects of the script to be modified and # 13 | # custom commands to be run on devices. # 14 | # LICENSE: GPLv2 # 15 | # AUTHORS: Jamie Nguyen # 16 | # Lukas B. # 17 | #=============================================================================# 18 | 19 | # Copyright (C) 2013-2020 Jamie Nguyen 20 | # Copyright (C) 2014 Lukas B. 21 | # 22 | # This program is free software; you can redistribute it and/or modify it 23 | # under the terms of the GNU General Public License v2 as published by the 24 | # Free Software Foundation. 25 | # 26 | # This program is distributed in the hope that it will be useful, but WITHOUT 27 | # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 28 | # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for 29 | # more details. 30 | # 31 | # You should have received a copy of the GNU General Public License along with 32 | # this program; if not, write to the Free Software Foundation, Inc., 33 | # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA 34 | 35 | if (( $# > 0 )) && [[ "$1" == "-v" || "$1" == "--version" ]]; then 36 | printf '%s\n' "$VERSION" 37 | exit 0 38 | fi 39 | 40 | declare -ri EXIT_CMDNOTFOUND=127 41 | declare -ri EXIT_CONFIG=78 42 | 43 | # {{{ ··· CONFIGURATION 44 | # Make sure that user defined options will not interfere with grep. 45 | unset GREP_OPTIONS 46 | 47 | # Set defaults. 48 | declare udisks="auto" 49 | declare mount_options="" 50 | declare -i show_internal=1 51 | declare -i show_removable=1 52 | declare -i show_optical=1 53 | declare -i show_commands=1 54 | declare -i colourize=1 55 | declare -i pretty=1 56 | declare -i custom4_show=0 57 | declare -i custom5_show=0 58 | declare -i custom6_show=0 59 | declare custom4_desc="" 60 | declare custom5_desc="" 61 | declare custom6_desc="" 62 | declare -i run_post_mount=0 63 | declare -i run_post_unmount=0 64 | declare -a exclude=() 65 | 66 | # Backwards compat 67 | declare default_mount_options="" 68 | declare -a blacklist=() 69 | 70 | filemanager() { 71 | ( cd "$1" && "$SHELL" ) 72 | } 73 | 74 | post_mount() { 75 | error "No 'post_mount' command specified in bashmount configuration file." 76 | return 1 77 | } 78 | 79 | post_unmount() { 80 | error "No 'post_unmount' command specified in bashmount configuration file." 81 | return 1 82 | } 83 | 84 | declare CONFIG_FILE="${XDG_CONFIG_HOME:-$HOME/.config}/bashmount/config" 85 | [[ ! -e "$CONFIG_FILE" ]] && CONFIG_FILE="/etc/bashmount.conf" 86 | if [[ -e "$CONFIG_FILE" ]]; then 87 | if ! . "$CONFIG_FILE"; then 88 | printf '%s\n' "bashmount: Failed to source configuration file." 89 | exit $EXIT_CONFIG 90 | fi 91 | fi 92 | 93 | if [[ "$udisks" != 0 ]]; then 94 | if type -p udisksctl >/dev/null 2>&1; then 95 | [[ "$udisks" == "auto" ]] && udisks=1 96 | elif [[ "$udisks" == "auto" ]]; then 97 | udisks=0 98 | else 99 | printf '%s\n' "bashmount: 'udisksctl': command not found" 100 | exit $EXIT_CMDNOTFOUND 101 | fi 102 | fi 103 | 104 | if ! type -p lsblk >/dev/null 2>&1; then 105 | printf '%s\n' "bashmount: 'lsblk': command not found" 106 | exit $EXIT_CMDNOTFOUND 107 | fi 108 | 109 | # Backwards compat 110 | [[ -n "$default_mount_options" ]] && mount_options="$default_mount_options" 111 | (( ${#exclude[@]} == 0 )) && exclude=("${blacklist[@]}") 112 | # }}} 113 | 114 | # {{{ ··· PRINTING FUNCTIONS 115 | if (( colourize )); then 116 | if tput setaf 0 >/dev/null 2>&1; then 117 | declare -r ALL_OFF="$(tput sgr0)" 118 | declare -r BOLD="$(tput bold)" 119 | declare -r BLUE="${BOLD}$(tput setaf 4)" 120 | declare -r GREEN="${BOLD}$(tput setaf 2)" 121 | declare -r RED="${BOLD}$(tput setaf 1)" 122 | else 123 | declare -r ALL_OFF="\e[1;0m" 124 | declare -r BOLD="\e[1;1m" 125 | declare -r BLUE="${BOLD}\e[1;34m" 126 | declare -r GREEN="${BOLD}\e[1;32m" 127 | declare -r RED="${BOLD}\e[1;31m" 128 | fi 129 | else 130 | declare -r ALL_OFF="" BOLD="" BLUE="" GREEN="" RED="" 131 | fi 132 | declare -r ARROW="==>" 133 | 134 | msg() { 135 | printf '\n%b\n\n' "${GREEN}${ARROW}${ALL_OFF}${BOLD} ${*}${ALL_OFF}" >&2 136 | } 137 | error() { 138 | printf '\n%b\n\n' "${RED}${ARROW}${BOLD} ERROR: ${*}${ALL_OFF}" >&2 139 | enter_to_continue 140 | } 141 | enter_to_continue() { 142 | printf '\n' 143 | read -r -e -p "Press [${BLUE}enter${ALL_OFF}] to continue: " 144 | } 145 | invalid_command() { 146 | printf '\n' 147 | error "Invalid command. See the help menu." 148 | } 149 | 150 | print_commands() { 151 | printf '\n\n' 152 | print_separator_commands 153 | printf '%s' " ${BLUE}e${ALL_OFF}: eject" 154 | printf '%s' " ${BLUE}i${ALL_OFF}: info" 155 | printf '%s' " ${BLUE}m${ALL_OFF}: mount" 156 | printf '%s' " ${BLUE}o${ALL_OFF}: open" 157 | printf '%s' " ${BLUE}u${ALL_OFF}: unmount" 158 | printf '\n\n' 159 | printf '%s' " ${BLUE}[Enter]${ALL_OFF}: refresh" 160 | printf '%s' " ${BLUE}a${ALL_OFF}: unmount all" 161 | printf '%s' " ${BLUE}q${ALL_OFF}: quit" 162 | printf '%s' " ${BLUE}?${ALL_OFF}: help" 163 | printf '\n\n' 164 | } 165 | 166 | print_submenu_commands() { 167 | printf '\n\n' 168 | print_separator_commands 169 | printf '%s' " ${BLUE}e${ALL_OFF}: eject" 170 | printf '%s' " ${BLUE}i${ALL_OFF}: info" 171 | if check_mounted "$devname"; then 172 | printf '%s' " ${BLUE}u${ALL_OFF}: unmount" 173 | else 174 | printf '%s' " ${BLUE}m${ALL_OFF}: mount " 175 | fi 176 | printf '%s' " ${BLUE}o${ALL_OFF}: open" 177 | printf '\n\n' 178 | printf '%s' " ${BLUE}[Enter]${ALL_OFF}: refresh" 179 | printf '%s' " ${BLUE}b${ALL_OFF}: back" 180 | printf '%s' " ${BLUE}q${ALL_OFF}: quit" 181 | printf '%s' " ${BLUE}?${ALL_OFF}: help" 182 | printf '\n\n' 183 | printf '%s' " ${BLUE}1${ALL_OFF}: luksDump" 184 | printf '%s' " ${BLUE}2${ALL_OFF}: luksOpen" 185 | printf '%s' " ${BLUE}3${ALL_OFF}: luksClose" 186 | printf '\n' 187 | 188 | if (( custom4_show )) || (( custom5_show )) || (( custom6_show )); then 189 | local -i col_width=18 190 | printf '\n' 191 | (( custom4_show )) && [[ -n "$custom4_desc" ]] \ 192 | && printf '%s' " ${BLUE}4${ALL_OFF}: $custom4_desc" 193 | local -i custom4_desc_len="${#custom4_desc}" 194 | if (( custom4_desc_len < col_width )); then 195 | for (( i=18; i>custom4_desc_len; i-- )); do 196 | printf '%s' " " 197 | done 198 | fi 199 | (( custom5_show )) && [[ -n "$custom5_desc" ]] \ 200 | && printf '%s' " ${BLUE}5${ALL_OFF}: $custom5_desc" 201 | local -i custom5_desc_len="${#custom5_desc}" 202 | if (( custom5_desc_len < col_width )); then 203 | for (( i=18; i>custom5_desc_len; i-- )); do 204 | printf '%s' " " 205 | done 206 | fi 207 | (( custom6_show )) && [[ -n "$custom6_desc" ]] \ 208 | && printf '%s' " ${BLUE}6${ALL_OFF}: $custom6_desc" 209 | printf '\n' 210 | fi 211 | } 212 | 213 | __print() { 214 | printf "${BOLD}" 215 | if (( pretty )); then 216 | printf '%s\n\n' "$1" | sed -e "s/-/━/g" 217 | else 218 | printf '%s\n\n' "$1" 219 | fi 220 | printf "${ALL_OFF}" 221 | } 222 | print_separator() { 223 | __print "-----------------------------------------------------------------------------" 224 | } 225 | print_separator_commands() { 226 | __print "-------------------------------- Commands ---------------------------------" 227 | } 228 | print_separator_device() { 229 | __print "------------------------------- Device Menu -------------------------------" 230 | } 231 | print_separator_optical() { 232 | __print "------------------------------ Optical Media ------------------------------" 233 | } 234 | print_separator_removable() { 235 | __print "----------------------------- Removable Media -----------------------------" 236 | } 237 | print_separator_internal() { 238 | __print "----------------------------- Internal Media ------------------------------" 239 | } 240 | 241 | print_help() { 242 | clear 243 | print_commands 244 | print_separator 245 | printf '%b' " ${GREEN}${ARROW}${ALL_OFF} " 246 | printf '%s' "${BOLD}To mount the first device, enter ${ALL_OFF}" 247 | printf '%s' "${BLUE}1m${ALL_OFF}${BOLD}.${ALL_OFF}" 248 | printf '\n\n' 249 | printf '%b' " ${GREEN}${ARROW}${ALL_OFF} " 250 | printf '%s' "${BOLD}To open the mountpath directory of the first${ALL_OFF}" 251 | printf '\n\n' 252 | printf '%s' " ${BOLD}device (mounting if required), enter " 253 | printf '%s' "${BLUE}1o${ALL_OFF}${BOLD}.${ALL_OFF}" 254 | printf '\n\n' 255 | printf '%b' " ${GREEN}${ARROW}${ALL_OFF} " 256 | printf '%s' "${BOLD}To view a device sub-menu, just enter the number.${ALL_OFF}" 257 | printf '\n\n' 258 | printf '%b' " ${GREEN}${ARROW}${ALL_OFF} " 259 | printf '%s' "${BLUE}[Enter]${ALL_OFF}" 260 | printf '%s' "${BOLD}, " 261 | printf '%s' "${BLUE}a${ALL_OFF}" 262 | printf '%s' "${BOLD}, " 263 | printf '%s' "${BLUE}q${ALL_OFF} " 264 | printf '%s' "${BOLD}and " 265 | printf '%s' "${BLUE}?${ALL_OFF} " 266 | printf '%s' "${BOLD}do not require a number.${ALL_OFF}" 267 | printf '\n\n' 268 | print_separator 269 | enter_to_continue 270 | } 271 | 272 | print_help_submenu() { 273 | clear 274 | print_submenu_commands 275 | printf '\n' 276 | print_separator 277 | printf '%b' " ${GREEN}${ARROW}${ALL_OFF} " 278 | printf '%s' "${BOLD}To perform a command, enter a character and press ${ALL_OFF}" 279 | printf '%s' "${BLUE}[Enter]${ALL_OFF}${BOLD}.${ALL_OFF}" 280 | printf '\n\n' 281 | printf '%b' " ${GREEN}${ARROW}${ALL_OFF} " 282 | printf '%s' "${BOLD}For example, to mount this device, type ${ALL_OFF}" 283 | printf '%s' "${BLUE}m${ALL_OFF} and press ${BLUE}[Enter]${ALL_OFF}" 284 | printf '%s' "${BOLD}.${ALL_OFF}" 285 | printf '\n\n' 286 | print_separator 287 | enter_to_continue 288 | } 289 | 290 | print_device() { 291 | local -i padding_name=13 292 | local -i padding_label=18 293 | local -i padding_size=6 294 | 295 | local label="$(info_fslabel "$devname")" 296 | local fstype="$(info_fstype "$devname")" 297 | 298 | [[ -z "$label" ]] && (( $# == 1 )) && [[ "$1" == "optical" ]] \ 299 | && label="$(lsblk -dno MODEL "$devname")" 300 | [[ -z "$label" ]] && label="$(info_partlabel "$devname")" 301 | [[ -z "$label" ]] && [[ "$fstype" == "crypto_LUKS" ]] && label="crypto_LUKS" 302 | [[ -z "$label" ]] && label="-" 303 | 304 | label="$(printf '%s' "$label" | sed -e 's/\\x20/ /g')" 305 | 306 | listed[device_number]="$devname" 307 | (( device_number++ )) 308 | 309 | printf '%s' " ${BLUE}${device_number})${ALL_OFF}" 310 | 311 | devname_short="${devname##*/}" 312 | label_short="$label" 313 | if (( ${#devname_short} > padding_name )); then 314 | len=$(( padding_name - 4 )) 315 | devname_short="${devname_short:0:len}..." 316 | elif (( ${#label} > padding_label )); then 317 | label_len=$(( padding_label - 4 )) 318 | label_short="${label:0:label_len}..." 319 | fi 320 | 321 | printf '%s' " ${devname_short}:" 322 | for (( i=padding_name; i>${#devname_short}; i-- )); do 323 | printf '%s' " " 324 | done 325 | 326 | printf '%s' " $label_short" 327 | for (( i=padding_label; i>${#label_short}; i-- )); do 328 | printf '%s' " " 329 | done 330 | 331 | size="$(info_size "$devname")" 332 | printf '%s' " $size" 333 | if (( ${#size} < padding_size )); then 334 | for (( i=padding_size; i>${#size}; i-- )); do 335 | printf '%s' " " 336 | done 337 | fi 338 | 339 | if [[ "$fstype" == "crypto_LUKS" ]]; then 340 | local uuid="$(info_uuid "$devname")" 341 | if [[ -n "$uuid" ]]; then 342 | for dev in "${all[@]}"; do 343 | if [[ "$dev" == "/dev/mapper/luks-$uuid" ]]; then 344 | printf '%s' " ${GREEN}decrypted [luks-${uuid:0:4}...]${ALL_OFF}" 345 | fi 346 | done 347 | fi 348 | elif check_mounted "$devname"; then 349 | mountpath="$(info_mountpath "$devname")" 350 | printf '%s' " ${GREEN}[$mountpath]${ALL_OFF}" 351 | mounted[${#mounted[*]}]="$devname" 352 | fi 353 | printf '\n' 354 | } 355 | # }}} 356 | 357 | # {{{ ··· INFORMATION RETRIEVAL 358 | # Returns 0 (ie, success) if the device still exists. Otherwise it returns 1. 359 | check_device() { 360 | if [[ ! -b "$1" ]]; then 361 | error "$1 is no longer available." 362 | return 1 363 | fi 364 | } 365 | # Returns 0 (ie, success) if the device is mounted. Otherwise it returns 1. 366 | check_mounted() { 367 | findmnt -no TARGET "$1" >/dev/null 2>&1 368 | } 369 | # Returns 0 (ie, success) if the device is registered as a removable device in 370 | # the kernel. Otherwise it returns 1. 371 | check_removable() { 372 | [[ "$(lsblk -drno RM "$1")" == "1" ]] 373 | } 374 | 375 | info_fslabel() { 376 | lsblk -drno LABEL "$1" 2>/dev/null 377 | } 378 | info_fstype() { 379 | lsblk -drno FSTYPE "$1" 2>/dev/null 380 | } 381 | info_mountpath() { 382 | findmnt -no TARGET "$1" 2>/dev/null 383 | } 384 | info_partlabel() { 385 | lsblk -drno PARTLABEL "$1" 2>/dev/null 386 | } 387 | info_size() { 388 | lsblk -drno SIZE "$1" 2>/dev/null 389 | } 390 | info_type() { 391 | lsblk -drno TYPE "$1" 2>/dev/null 392 | } 393 | info_uuid() { 394 | lsblk -drno UUID "$1" 2>/dev/null 395 | } 396 | info_used() { 397 | lsblk -drno FSUSE% "$1" 2>/dev/null 398 | } 399 | 400 | get_luks_child() { 401 | local uuid="$(info_uuid "$1")" 402 | printf '%s' "/dev/mapper/luks-$uuid" 403 | } 404 | # }}} 405 | 406 | # {{{ ··· DEVICE MANIPULATION 407 | __mount() { 408 | msg "Mounting $1 ..." 409 | if (( udisks )); then 410 | udisksctl mount $mount_options --block-device "$1" 411 | else 412 | read -r -e -p "Choose the mountpoint directory: " dir 413 | [[ -z "$dir" ]] && return 1 414 | if [[ ! -e "$dir" ]]; then 415 | if ! mkdir -p "$dir"; then 416 | error "'$dir': Could not create directory." 417 | return 1 418 | fi 419 | fi 420 | sudo mount $mount_options "$1" "$dir" 421 | fi 422 | } 423 | 424 | __unmount() { 425 | msg "Unmounting $1 ..." 426 | if (( udisks )); then 427 | udisksctl unmount --block-device "$1" 428 | else 429 | sudo umount "$1" 430 | fi 431 | } 432 | 433 | action_eject() { 434 | check_device "$1" || return 1 435 | 436 | if [[ "$(info_fstype "$1")" == "crypto_LUKS" ]]; then 437 | action_unmount "$1" || return 1 438 | else 439 | check_mounted "$1" && action_unmount "$1" 440 | fi 441 | 442 | local -i retval=0 443 | if ! check_mounted "$1"; then 444 | msg "Ejecting $1 ..." 445 | device_type=$(info_type "$1") 446 | if (( udisks )) && [[ "$device_type" != "rom" ]]; then 447 | udisksctl power-off -b "$1" 448 | retval=$? 449 | else 450 | sudo eject "$1" 451 | retval=$? 452 | fi 453 | if (( retval == 0 )); then 454 | # Give the device some time to eject. 455 | sleep 1.5s 456 | else 457 | enter_to_continue 458 | fi 459 | fi 460 | } 461 | 462 | action_info() { 463 | check_device "$1" || return 1 464 | if (( udisks )); then 465 | udisksctl info -b "$devname" | less 466 | else 467 | lsblk -po NAME,FSTYPE,SIZE,FSUSE%,MOUNTPOINT "$1" | less 468 | fi 469 | } 470 | 471 | action_mount() { 472 | check_device "$1" || return 1 473 | if check_mounted "$1"; then 474 | error "$1 is already mounted." 475 | return 1 476 | fi 477 | 478 | if [[ "$(info_fstype "$1")" == "crypto_LUKS" ]]; then 479 | luks_child="$(get_luks_child "$1")" 480 | if [[ ! -b "$luks_child" ]]; then 481 | action_unlock "$1" || return 1 482 | fi 483 | action_mount "$luks_child" 484 | return $? 485 | fi 486 | 487 | if __mount "$1"; then 488 | msg "$1 mounted successfully." 489 | (( run_post_mount )) && post_mount "$1" 490 | sleep 0.1s 491 | return 0 492 | fi 493 | error "$1 could not be mounted." 494 | return 1 495 | } 496 | 497 | action_open() { 498 | check_device "$1" || return 1 499 | if [[ "$(info_fstype "$1")" == "crypto_LUKS" ]]; then 500 | luks_child="$(get_luks_child "$1")" 501 | if [[ ! -b "$luks_child" ]]; then 502 | action_mount "$1" || return 1 503 | fi 504 | action_open "$luks_child" 505 | return $? 506 | elif ! check_mounted "$1"; then 507 | action_mount "$1" || return 1 508 | fi 509 | msg "Opening $1 ..." 510 | filemanager "$(info_mountpath "$1")" || enter_to_continue 511 | } 512 | 513 | action_unmount() { 514 | check_device "$1" || return 1 515 | 516 | if [[ "$(info_fstype "$1")" == "crypto_LUKS" ]]; then 517 | luks_child="$(get_luks_child "$1")" 518 | if [[ -b "$luks_child" ]]; then 519 | if check_mounted "$luks_child"; then 520 | action_unmount "$luks_child" || return 1 521 | fi 522 | action_lock "$1" || return 1 523 | fi 524 | return $? 525 | fi 526 | 527 | if ! check_mounted "$1"; then 528 | error "$1 is already unmounted." 529 | return 1 530 | fi 531 | 532 | if __unmount "$1"; then 533 | msg "$1 unmounted successfully." 534 | (( run_post_unmount )) && post_unmount "$1" 535 | sleep 0.1s 536 | return 0 537 | fi 538 | error "$1 could not be unmounted." 539 | return 1 540 | } 541 | 542 | action_unlock() { 543 | msg "Opening luks volume ..." 544 | local -i retval=0 545 | if (( udisks )); then 546 | udisksctl unlock --block-device "$devname" 547 | retval=$? 548 | else 549 | sudo cryptsetup open --type luks -v "$devname" "luks-${devname##*/}" 550 | retval=$? 551 | fi 552 | (( retval != 0 )) && enter_to_continue 553 | return $retval 554 | } 555 | 556 | action_lock() { 557 | msg "Closing luks volume ..." 558 | local -i retval=0 559 | if (( udisks )); then 560 | udisksctl lock --block-device "$devname" 561 | retval=$? 562 | else 563 | sudo cryptsetup close --type luks "$devname" 564 | retval=$? 565 | fi 566 | (( retval != 0 )) && enter_to_continue 567 | return $retval 568 | } 569 | # }}} 570 | 571 | # {{{ ··· MENU FUNCTIONS 572 | list_devices() { 573 | all=() # all devices 574 | listed=() # all devices that are shown (ie, not hidden) 575 | mounted=() # all devices that are shown and mounted 576 | device_number=0 577 | 578 | local -a removable=() 579 | local -a internal=() 580 | local -a optical=() 581 | 582 | while IFS='' read -r device; do 583 | all+=( "$device" ) 584 | done < <(lsblk -plno NAME) 585 | 586 | for devname in "${all[@]}"; do 587 | # Hide excluded devices. 588 | for string in "${exclude[@]}"; do 589 | lsblk -dPno NAME,TYPE,FSTYPE,LABEL,MOUNTPOINT,PARTLABEL,UUID "$devname" \ 590 | | grep -E "$string" >/dev/null 2>&1 591 | (( $? )) || continue 2 592 | done 593 | 594 | # Sort devices into arrays removable, internal, and optical. 595 | local device_type=$(info_type "$devname") 596 | if [[ "$device_type" == "part" ]]; then 597 | if check_removable "$devname"; then 598 | removable[${#removable[*]}]="$devname" 599 | else 600 | internal[${#internal[*]}]="$devname" 601 | fi 602 | elif [[ "$device_type" == "crypt" ]]; then 603 | # luks-xxxxx devices are never marked as removable, so we judge 604 | # whether they are removable by their parent crypto_LUKS device. 605 | local -i parent_found=0 606 | for parent_devname in "${all[@]}"; do 607 | local parent_uuid="$(info_uuid "$parent_devname")" 608 | [[ -z "$parent_uuid" ]] && continue 609 | if [[ "/dev/mapper/luks-$parent_uuid" == "$devname" ]]; then 610 | if check_removable "$parent_devname"; then 611 | removable[${#removable[*]}]="$devname" 612 | parent_found=1 613 | break 614 | else 615 | internal[${#internal[*]}]="$devname" 616 | parent_found=1 617 | break 618 | fi 619 | fi 620 | done 621 | (( !parent_found )) && internal[${#internal[*]}]="$devname" 622 | # Normally we don't want to see a "disk", but if it has no partitions 623 | # (eg, internal storage on some portable media devices) then it should 624 | # be visible. 625 | elif [[ "$device_type" == "disk" ]]; then 626 | for (( i=0; i<${#all[@]}; i++ )); do 627 | [[ "${all[$i]}" =~ "$devname".+ ]] && continue 2 628 | done 629 | if check_removable "$devname"; then 630 | removable[${#removable[*]}]="$devname" 631 | else 632 | internal[${#internal[*]}]="$devname" 633 | fi 634 | elif [[ "$device_type" == "rom" ]]; then 635 | optical[${#optical[*]}]="$devname" 636 | else 637 | continue 638 | fi 639 | done 640 | 641 | clear 642 | # List internal media. 643 | if (( show_internal )) && (( ${#internal[*]} )); then 644 | print_separator_internal 645 | for devname in "${internal[@]}"; do 646 | print_device 647 | done 648 | printf '\n' 649 | fi 650 | # List removable media. 651 | if (( show_removable )) && (( ${#removable[*]} )); then 652 | print_separator_removable 653 | for devname in "${removable[@]}"; do 654 | print_device 655 | done 656 | printf '\n' 657 | fi 658 | # List optical media. 659 | if (( show_optical )) && (( ${#optical[*]} )); then 660 | print_separator_optical 661 | for devname in "${optical[@]}"; do 662 | print_device optical 663 | done 664 | printf '\n' 665 | fi 666 | (( !device_number )) && printf '%s\n' "No devices." 667 | } 668 | 669 | submenu() { 670 | # Make sure device is still valid. 671 | check_device "$devname" || return 1 672 | 673 | # Try to use a useful label to identify the device. 674 | local label="$(info_fslabel "$devname")" 675 | [[ -z "$label" ]] && label="$(info_partlabel "$devname")" 676 | [[ -z "$label" ]] && label="-" 677 | 678 | local fstype="$(info_fstype "$devname")" 679 | local size="$(info_size "$devname")" 680 | local used="$(info_used "$devname")" 681 | local uuid="$(info_uuid "$devname")" 682 | 683 | local -i mounted=0 684 | check_mounted "$devname" && mounted=1 685 | 686 | # Display the user interface. 687 | clear 688 | print_separator_device 689 | printf '%s\n' " device : $devname" 690 | printf '%s\n' " label : $label" 691 | if [[ "$fstype" == "crypto_LUKS" ]]; then 692 | printf '%s' " status : " 693 | local -i unlocked=0 694 | if [[ -n "$uuid" ]]; then 695 | for dev in "${all[@]}"; do 696 | if [[ "$dev" == "/dev/mapper/luks-$uuid" ]]; then 697 | printf '%s\n' "${GREEN}decrypted [luks-${uuid:0:4}...]${ALL_OFF}" 698 | unlocked=1 699 | break 700 | fi 701 | done 702 | fi 703 | (( !unlocked )) && printf '%s\n' "encrypted" 704 | else 705 | printf '%s' " mounted : " 706 | if (( mounted )); then 707 | printf '%s\n' "${GREEN}yes${ALL_OFF}" 708 | printf '%s\n' " mountpath : $(info_mountpath "$devname")" 709 | else 710 | printf '%s\n' "${RED}no${ALL_OFF}" 711 | fi 712 | fi 713 | printf '%s\n' " fstype : $fstype" 714 | printf '%s\n' " uuid : $uuid" 715 | printf '%s\n' " size : $size" 716 | (( mounted )) && printf '%s\n' " used : $used" 717 | if (( show_commands == 1 )); then 718 | printf '\n' 719 | print_submenu_commands 720 | fi 721 | printf '\n' 722 | print_separator 723 | 724 | # Receive user input. 725 | local -i retval 726 | if ! read -r -e -p "${BOLD}Command:${ALL_OFF} " action; then 727 | # Exit on ctrl-d. 728 | printf '\n' 729 | exit 0 730 | fi 731 | case "$action" in 732 | "e") action_eject "$devname" || true;; 733 | "i") action_info "$devname" || true;; 734 | "m") action_mount "$devname" || true;; 735 | "o") action_open "$devname" || true;; 736 | "u") action_unmount "$devname" || true;; 737 | "b") return 1;; 738 | ""|"r") return 0;; 739 | "q") exit;; 740 | "?") print_help_submenu; return 0;; 741 | "1") sudo sh -c "cryptsetup luksDump '$devname' | less"; return 0;; 742 | "2") action_unlock "$devname"; return 1;; 743 | "3") action_lock "$devname"; return 1;; 744 | "4") 745 | if (( custom4_show )); then 746 | msg "Running custom command: $custom4_desc ..." 747 | custom4_command "$devname" 748 | enter_to_continue 749 | else 750 | invalid_command 751 | fi 752 | return 0;; 753 | "5") 754 | if (( custom5_show )); then 755 | msg "Running custom command: $custom5_desc ..." 756 | custom5_command "$devname" 757 | enter_to_continue 758 | else 759 | invalid_command 760 | fi 761 | return 0;; 762 | "6") 763 | if (( custom6_show )); then 764 | msg "Running custom command: $custom6_desc ..." 765 | custom6_command "$devname" 766 | enter_to_continue 767 | else 768 | invalid_command 769 | fi 770 | return 0;; 771 | *) invalid_command; return 0;; 772 | esac 773 | } 774 | 775 | select_action() { 776 | local devname 777 | local letter 778 | print_separator 779 | if ! read -r -e -p "${BOLD}Command:${ALL_OFF} " action; then 780 | # Exit on ctrl-d. 781 | printf '\n' 782 | exit 0 783 | fi 784 | if [[ "$action" =~ ^[1-9] ]]; then 785 | if [[ "$action" =~ ^[1-9][0-9]*$ ]]; then 786 | # Zero-based numbering for array elements, so subtract one. 787 | local number="$(( action - 1 ))" 788 | if (( number >= device_number )); then 789 | invalid_command 790 | return 1 791 | fi 792 | devname=${listed[number]} 793 | while true; do 794 | submenu || break 795 | done 796 | elif [[ "$action" =~ ^[1-9][0-9]*[eimou]$ ]]; then 797 | # Zero-based numbering for array elements, so subtract one. 798 | local number="$(( ${action%?} - 1 ))" 799 | 800 | if (( number >= device_number )); then 801 | invalid_command 802 | return 1 803 | fi 804 | devname="${listed[number]}" 805 | 806 | letter="${action: -1}" 807 | case "$letter" in 808 | "e") action_eject "$devname";; 809 | "i") action_info "$devname";; 810 | "m") action_mount "$devname";; 811 | "o") action_open "$devname";; 812 | "u") action_unmount "$devname";; 813 | *) return 1;; 814 | esac 815 | return 0 816 | else 817 | invalid_command 818 | return 1 819 | fi 820 | else 821 | case "$action" in 822 | "a") 823 | if (( ! ${#mounted[*]} )); then 824 | error "No devices mounted." 825 | return 1 826 | fi 827 | printf '\n' 828 | read -r -e -p "Unmount all devices [y/N]?: " unmount 829 | [[ "$unmount" != "y" ]] && [[ "$unmount" != "Y" ]] && return 0 830 | clear 831 | for devname in "${mounted[@]}"; do 832 | action_unmount "$devname" || continue 833 | done 834 | enter_to_continue 835 | return 1;; 836 | "r"|"") return 0;; 837 | "q"|"b") exit 0;; 838 | "?") print_help; return 0;; 839 | *) invalid_command; return 1;; 840 | esac 841 | fi 842 | } 843 | # }}} 844 | 845 | declare -i device_number=0 846 | declare -a all=() 847 | declare -a listed=() 848 | declare -a mounted=() 849 | 850 | while true; do 851 | list_devices 852 | (( show_commands )) && print_commands 853 | select_action 854 | done 855 | --------------------------------------------------------------------------------