Gentoo Forums
Gentoo Forums
Gentoo Forums
Quick Search: in
Auto remove kernel function
View unanswered posts
View posts from last 24 hours

 
Reply to topic    Gentoo Forums Forum Index Portage & Programming
View previous topic :: View next topic  
Author Message
irenicus09
Tux's lil' helper
Tux's lil' helper


Joined: 07 Jun 2013
Posts: 118

PostPosted: Fri Apr 14, 2017 4:28 pm    Post subject: Auto remove kernel function Reply with quote

So I'm learning bash scripting, I decided to create a function to automate removal of specific kernel.

I just added this code to my .zshrc

Code:

# Example: rmkern 4.9.6
rmkern() {
    eselect_version=$(eselect kernel list | grep $1 | cut -d'*' -f1 | cut -d']' -f2) | tr -d "[:space:]";
    kern_version=$(echo $eselect_version | cut -d '-' -f2);
    module_version=$(printf "%s-gentoo" $kern_version);
    revision=$(echo $eselect_version | cut -d '-' -f4);
    portage_version=$(echo "sys-kernel/gentoo-sources-$kern_version");

    if [ ! -z $revision ]; then # If string not null
        portage_version=$(printf "%s-%s" $portage_version $revision);
    fi

    emerge -aC $portage_version;
    find /usr/src/ -name "*$kern_version*" -type d -exec rm -rf "{}" "+"
    rm -rf /lib/modules/$module_version;
    find /boot -name "*$kern_version*" -exec rm -rf "{}" "+"
    grub-mkconfig -o /boot/grub/grub.cfg;
}


The code assumes that the boot partition is mounted.

I'd like some feedback on how to further improve the code, what best practices I can stick to or if I'm doing something wrong.

I don't mind criticism, as this is always a good opportunity learn and improve myself.

Thanks :P
Back to top
View user's profile Send private message
Sadako
Advocate
Advocate


Joined: 05 Aug 2004
Posts: 3792
Location: sleeping in the bathtub

PostPosted: Fri Apr 14, 2017 6:04 pm    Post subject: Reply with quote

Ok, first off, some shell basic shell scripting guidlines;
Shell scripts don't require semicolon ( ; ) at the end of a line, it's only really needed to separate two commands if they are on the same line, iow you need either a semicolon OR line break between commands.
Not a requirement, but typically variable names in shell scripts are all uppercase.
While not strictly required, generally you'd want to put commands in $() within doublequotes, ie eselect_version="$(eselect kernel list)", if nothing else it certainly improves readability.

Using 'eselect kernel' seems ugly to me, all that really does is list the kernel sources you have under /usr/src anyways, and it (probably?) doesn't list kernels you've unmerged, even though files genereated when you configure and compile the kernel remain.

module_version won't be terribly effective, you can set a string to append to kernel version in kernel config, and that string will be appended to kernel modules directory under /lib/modules, so you'd prob need to add a wildcard for that, ie
Code:
rm -rf -- "/lib/modules/${MODULE_VERSION}"*


As for using the rm command in a script, I would VERY STRONGLY recommend use -I rather than -f, particulary since this while be run as root, each time it's run it'll ask you once if you want to delete or not, it wont be as fully automated but the required user input will be very minimal.

Code:
if [ ! -z $revision ]; then # If string not null
         portage_version=$(printf "%s-%s" $portage_version $revision);
fi
If you're testing a variable to see if it's unset or not after a command, then you should add 'unset revision' as pretty much the first command within your function, otherwise if the variable was somehow defined in your shell before calling the function it'd wouldn't behave as expected.

And the portage_version statement there can easily be replaced with a simple portage_version="${portage_version}-$revision)"
Similarly, module_version=$(printf "%s-gentoo" $kern_version); above that can be replaced with module_version="${kern_version}-gentoo", although not hardcoding that means this will only every work with sys-kernel/gentoo-sources kernels.

The find commands are unneccessary, they can simply be replaced with
Code:
rm -I -- /boot/*"${kern_version}"*"
rm -rI -- /usr/src/*"${kern_version}"*
I would be very careful with those wildcards, though, you should probe make absolutely sure the kern_version variable isn't null before those commands.

Those are just some pointers, hopefully helpful and don't sound too critical.

A final note, though, I would actually rethink the entire approach, it won't as effective if you've already removed the sources from /usr/src but the kernel under /boot and the modules remain.

Something like the following for /boot and /lib/modules seems like a better (and simpler?) option;
Code:
rmkern() {
    # to be safe, just return if no arguments are passed
    [ "${1}" ] || return

    for BZIMAGE in /boot/*"${1}"*; do
        [ -f "${BZIMAGE}" ] && rm -I -- "${BZIMAGE}"
    done

    for MODULES in /lib/modules/*"${1}"*/; do
        [ -d "${MODULES}" ] && rm -rI -- "${MODULES}"
    done

    # stuff to handle sources under /usr/src goes here :P
}
Of course, that might not serve your purposes at all (and I'm probably overcautious).
Also, I didn't really test the above. :P
_________________
"You have to invite me in"
Back to top
View user's profile Send private message
steveL
Watchman
Watchman


Joined: 13 Sep 2006
Posts: 5153
Location: The Peanut Gallery

PostPosted: Fri Apr 14, 2017 7:01 pm    Post subject: Reply with quote

Sadako wrote:
Not a requirement, but typically variable names in shell scripts are all uppercase.
No. Environment variables are typically all uppercase. Shell parameters (aka variables) local to the script are usually kept lower-case.
Quote:
While not strictly required, generally you'd want to put commands in $() within doublequotes, ie eselect_version="$(eselect kernel list)", if nothing else it certainly improves readability.
It is usually required, in fact; assignment is one of only two places where shell is defined not to field-split, however.

You don't mention quoting, perhaps the most fundamental aspect of shell-scripting, at all otherwise, when there are many places it is missing.
Code:
if [ ! -z $revision ]; then
is just awful.
Code:
if [ -n "$revision" ]; then
Both of you need to be hanging out in #bash on IRC: chat.freenode.net
They will teach you POSIX sh as well, if you specify that upfront.
Back to top
View user's profile Send private message
Sadako
Advocate
Advocate


Joined: 05 Aug 2004
Posts: 3792
Location: sleeping in the bathtub

PostPosted: Fri Apr 14, 2017 8:01 pm    Post subject: Reply with quote

steveL wrote:
Sadako wrote:
Not a requirement, but typically variable names in shell scripts are all uppercase.
No. Environment variables are typically all uppercase. Shell parameters (aka variables) local to the script are usually kept lower-case.
Huh, interesting.
Pretty much every example I've seen has used all uppercase for all variables, which I assumed was the norm and have adopted myself for years.

I do find all caps helpfull wrt readability, but that's something I'll keep in mind in future.

Quote:
You don't mention quoting, perhaps the most fundamental aspect of shell-scripting, at all otherwise, when there are many places it is missing.
Agreed, I deliberately avoided stressing that so as not to seem too critical though, tbh.
_________________
"You have to invite me in"
Back to top
View user's profile Send private message
cboldt
Veteran
Veteran


Joined: 24 Aug 2005
Posts: 1046

PostPosted: Fri Apr 14, 2017 9:41 pm    Post subject: Reply with quote

If you are aiming to find part of the package name with eselect, consider this instead of a series of `cut` commands:

Code:
[ "$1" == "" ] && exit
kern_version=$(eselect kernel list | grep -o "linux.*$1.* ")     # Note trailing space in search string
if [ "$?" == "0" ]; then
  module_version=${eselect_version#*-}
  ...
else
  echo You provided an invalid kernel version
fi


The above snippet includes two error checks, stopping if $1 is empty, and stopping if $kern_version is empty, tested by the grep command returning other than errorlevel 0.

You have an extraneous `echo` in your determination of $revision, but using the above, I think $revision is part of $kern_version and $module_version anyway, not having been split out using `cut` with hyphen as the delimiter.

Use of quotes when checking for empty strings has been pointed out. If you don't have quotes, the script will error out if the variable is empty.

I'd skip the `find /usr/src` and just directly run `rm -rf /usr/src/$eselect_version`, and similar with /boot/*$kern_version*

If you always have /boot mounted, I wouldn't sweat not testing for it. If you don't have /boot always mounted, test and mount if necessary. That is pretty easy.

Code:
[ -f /boot/lost+found ] || mount /boot


Your code for testing the mount condition of /boot may vary, depending on what you use for a filesystem at /boot. Just identify a file that is persistent in /boot, that does not change. Put one in, if you want, for example /boot/.keep

If your /boot is normally unmounted, I would have the script do this before finishing ...

Code:
ls -l /boot
umount /boot
[/code]
Back to top
View user's profile Send private message
cboldt
Veteran
Veteran


Joined: 24 Aug 2005
Posts: 1046

PostPosted: Fri Apr 14, 2017 10:17 pm    Post subject: Reply with quote

Another toy ...

Code:
kern_directory=$(stat -c %n /usr/src/linux-$1*)
module_dir=/lib/modules/${kern_directory#*-}


I think I was mistaken before, about $revision. That string appears in different locations depending on the context.

gentoo-sources-A.B.C$revision # for example, $revision=-r2
/usr/src/linux-A.B.C-gentoo$revision
/lib/modules/A.B.C-gentoo$revision

For deletion of the source code directory and associated module directory, there is no need to separate $revision, that information is part of $kern_directory and $module_dir, and in the right location. But $revision does have to be relocated for the `emerge -aC gentoo-soures-A.B.C$revision` command.

I'm not sure how I'd end up doing that, but this comes to mind

Code:
revision=${kern_directory##-*}
if [ "${revision}" == "gentoo" ]; then
  revision=""
else
  revision="-${revision}"           # Add the hyphen
fi


All of this bash substring action avoids calling external program "cut"

There is a condition that this script fails on, which is when there is more than one /usr/src/linux-A.B.C directory, that is, more than one revision level for the same kernel version.
Back to top
View user's profile Send private message
khayyam
Watchman
Watchman


Joined: 07 Jun 2012
Posts: 6227
Location: Room 101

PostPosted: Fri Apr 14, 2017 10:26 pm    Post subject: Re: Auto remove kernel function Reply with quote

irenicus09 wrote:
So I'm learning bash scripting, I decided to create a function to automate removal of specific kernel. I just added this code to my .zshrc

irenicus09 ... mostly this will work, zsh is a "bourne" shell derivative, however some things are different, ie, zsh arrays begin at 1, whereas bash begins at 0 (because ... stupidness). Here is how I would write your function[1] in zsh (note I've commented the commands, and replaced with 'ls' for this test).

Code:
rmkern() {
local IFS="-" version kern_version kern_pkg revision pkg_version
version=($(echo $1))
kern_version="${version[2]}"
kern_pkg="${version[3]}"
revision="${version[4]}"
pkg_version="=sys-kernel/${kern_pkg}-sources-${kern_version}"

if [[ -n "$revision" ]] ; then
    pkg_version="${pkg_version}-${revision}"
fi

if [[ -d "/usr/src/linux-${kern_version}-${kern_pkg}" ]] ; then
    #emerge -aC "$pkg_version"
    #rm -f "/usr/src/linux-${kern_version}-${kern_pkg}"
    #rm -rf "/lib/modules/${kern_version}-${kern_pkg}"
    ls -ld "/usr/src/linux-${kern_version}-${kern_pkg}"
    ls -ld "/lib/modules/${kern_version}-${kern_pkg}"
        if ! (grep -qs '/boot' /proc/mounts) ; then
            mount /boot || echo "mounting /boot failed"
        else
            ls -l /boot/*"${kern_version}"*
            (($+commands[grub-mkconfig])) && grub-mkconfig -o /boot/grub/grub.cfg
        fi     
fi
}

Code:
% source rmkern.zsh
% rmkern linux-3.12.72-ck
drwxr-xr-x 25 root root 4096 2017-04-10 03:34 /usr/src/linux-3.12.72-ck/
drwxr-xr-x 3 root root 4096 2017-04-06 15:32 /lib/modules/3.12.72-ck/
-rwxr-xr-x 1 root root 4613072 2017-04-06 15:31 /boot/vmlinuz-3.12.72-ck.efi

Here we use an array "($())" that is then used to populate our vars ... bash would be similar, but again the array begins at 0. The '(($+commands[grub-mkconfig]))' is zsh specific, zsh has an array 'commands' (that can be used to test if the command exists ... here using a math expression). The function may still be rough arround the edges (I didn't test well, and I would echo others comments re the use of 'rm -f') but it should work.

1). for zsh functions you can do something like this (and so enable/disable them with +x or -x respectively):

~/.zshrc:
# zdotdir
zdotdir=${ZDOTDIR:-$HOME}

# fpath
fpath=($zdotdir/{.zsh/*.zwc,.zsh/{functions,scripts}}(N) $fpath)

# Autoload functions that have the executable bit on.
# (functions are under $zdotdir/.zsh/functions)
for d in $fpath; do
  fns=( $d/*~*~(N.x:t) )
  (( ${#fns} )) && autoload $fns
done

edit: I should probably save, and reset, the value of IFS, but I'll leave that as an exercise for you as right now I'm pooped.

HTH & best ... khay
Back to top
View user's profile Send private message
irenicus09
Tux's lil' helper
Tux's lil' helper


Joined: 07 Jun 2013
Posts: 118

PostPosted: Sat Apr 15, 2017 1:02 pm    Post subject: Reply with quote

Thanks for all the helpful suggestion guys, let me take that all in and digest first.

From what I've read initially, it looks like I've been doing a lot of stuff wrong like not using quotes while testing conditions which seems to be important. Also I didn't realize that string manipulation can be done without using the cut command...and I thought using semicolon per command was mandatory which doesn't seem to be the case.

cboldt: Thanks for showing me interesting ways to handle errors also the use of '||' and '&&' operators in test cases which I wasn't aware could be used like that.

khayyam on the other hand went the extra mile to show me how to use zsh scripts, thanks man. Really appreciate it! :P

There's so much to learn from these forum posts and the experienced guys here, I feel like anytime I end up coding something interesting..I'll post here for feedback. Hope it is not too annoying :)
Back to top
View user's profile Send private message
cboldt
Veteran
Veteran


Joined: 24 Aug 2005
Posts: 1046

PostPosted: Sat Apr 15, 2017 1:13 pm    Post subject: Reply with quote

I've got another idea. Add a routine to /etc/portage/bashrc that fires on the "postrm" phase of `emerge -C gentoo-sources-${PVR}`

That way, portage does the testing for existence of that version, and the associated directory and file entries in /usr/src, /lib/modules, and /boot will be removed after portage removes (only) what it installed.

${PV} Contains the version number without the revision.
${PR} Contains the revision number or 'r0' if no revision number exists.
${PVR} Contains the version number with the revision.

My /etc/portage/bashrc has this sort of structure ...

Code:
case "$EBUILD_PHASE" in
        postinst)       post_install    ;;
        *)              :               ;;
esac

post_install()
{
case "${CATEGORY}/${PN}" in
        app-admin/mktwpol)
                touch /etc/mktwpol/mktwpol-local.cfg
        ;;

        *)      :
        ;;
esac
}


So, the proposed structure would add a "postrm" branch to the "case $EBUILD_PHASE" command, and add a subroutine (maybe called postrm, or post_rm), with a branch to case ${CATEGORY}/${PN}=sys-kernel/*-sources
Back to top
View user's profile Send private message
cboldt
Veteran
Veteran


Joined: 24 Aug 2005
Posts: 1046

PostPosted: Sat Apr 15, 2017 1:34 pm    Post subject: Reply with quote

Using quotes to test conditions avoids a script error in at least two cases. If those cases aren't possible, the quotes aren't necessary.

One case is where the variable being tested is empty. That results in a test that reads, for example, "[ -z ]" Bash can't handle that. when you enclose the variable in quotes, that "empty variable" test becomes [-z "" ] which results in an errorlevel of 0, the string has a length of zero and the test passes.

Another case is when the variable being tested has a space, tab, or newline.

Quoting is a good habit. There are very limited conditions where quoting causes the intended test to fail - and there are probably ways around them too. You'll know it when you bump into it. The example I have in mind is ...

Code:
ip_regex='([0-9]{1,3}\.){3}[0-9]{1,3}'
...
if [[ ! ${line[0]} =~ $ip_regex ]]; then ...


If I quote $ip_regex, the test doesn't work. Before the test is done, I "know" that neither ${line[0]} nor $ip_regex is empty.
Back to top
View user's profile Send private message
Display posts from previous:   
Reply to topic    Gentoo Forums Forum Index Portage & Programming All times are GMT
Page 1 of 1

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum