69

I have seen many softwares such as Update Manager and Synaptic Package Manager, they wait if some other program is using the /var/lib/dpkg/lock and is locked. How can we do this through the Terminal? I saw apt-get's manual but didn't find anything useful.

Seth
  • 58,122
Piyush
  • 813
  • well you can do several apt-get commands. Like sudo apt-get install packagename && sudo apt-get update and they will happen automatic after each others. – Alvar Nov 09 '13 at 11:10
  • 1
    Does this not do that: sudo apt-get install packagename1 packagename2 packagename3 ? – Rinzwind Nov 09 '13 at 11:45
  • (See https://unix.stackexchange.com/questions/463498/terminate-and-disable-remove-unattended-upgrade-before-command-returns if you are in the special case where you are trying to wait for boot time apt to finish on Ubuntu 18.04 and up) – Anon Nov 10 '18 at 17:29
  • 1
    apt does that in Ubuntu 20.04 and later, I suppose. See here – jarno May 14 '20 at 23:20

9 Answers9

59

You can make apt-get to learn to wait if another software manager is running. Something similar with the behaviour from the next screen cast:

enter image description here

How I made it?

I create a new script called apt-get (wrapper for apt-get) in /usr/local/sbin directory with the following bash code inside:

#!/bin/bash

i=0 tput sc while fuser /var/lib/dpkg/lock >/dev/null 2>&1 ; do case $(($i % 4)) in 0 ) j="-" ;; 1 ) j="\" ;; 2 ) j="|" ;; 3 ) j="/" ;; esac tput rc echo -en "\r[$j] Waiting for other software managers to finish..." sleep 0.5 ((i=i+1)) done

/usr/bin/apt-get "$@"

Don't forget to make it executable:

sudo chmod +x /usr/local/sbin/apt-get

Before to test, check if everything is ok. The output of which apt-get command should be now /usr/local/sbin/apt-get. The reason is: by default, the /usr/local/sbin directory is placed before /usr/bin directory in user or root PATH.

Radu Rădeanu
  • 169,590
  • thats really a direct hit to my question. thanks :) – Raja G Nov 10 '13 at 16:32
  • 2
    How your script manages stacked instances of apt-get? Like, before it finishes I launch 5 more apt-get? – Braiam Nov 11 '13 at 01:14
  • @Braiam Sincerely, I don't know; I'm not a good tester. Did you test it? If problems appears, the script can be improved by creating a new lock with timestamp when a new instance of the script starts (just an example - there are also many other ways). – Radu Rădeanu Nov 14 '13 at 21:03
  • wow! eloquent. works like a charm with everything I've thrown at it so far! Two thumbs up... this should just be part of the package :D – Eric Jul 04 '17 at 18:27
  • 3
    That's perfect! I've had problem where I use AWS cloudinit to install packages on a new server, but Ubuntu does some apt stuff on boot, so my dpkg command failed due to locked dpkg db. I've put this one-liner in my cloudinit userdata: while fuser /var/lib/dpkg/lock >/dev/null 2>&1; do sleep 1; done; dpkg -i package.deb and it works great. – Volodymyr Smotesko Jul 19 '17 at 14:45
  • using inotify would improve this script. – ctrl-alt-delor Sep 08 '17 at 17:04
  • I sorta want to use a snippet of this code in a larger script. However, I am not familiar with controlling terminal cursor and I don't understand why you need the tput sc and tput rc because I'd assume the cursor just automatically goes to the end of the line, anyway, right? And for the "loading bar" rewrite effect, that's covered by echo -en so can you explain what it's doing with the cursor? I tried testing it and somehow my VM system got all screwed up. Running two sudo bash testme.sh -y install dpkg bash gcc build-essential perl git-all autotools-dev cmake libffi-dev ; echo "$?"'s – Timothy Swan Feb 24 '18 at 19:20
  • I tested it without the tput lines and it restores the cursor anyway even when I try to type things. Are those necessary for output to logs or consoles or something? Or do some terminals just behave differently and you're being safe? – Timothy Swan Feb 24 '18 at 19:28
  • more effective to use grep -qFx /var/lib/dpkg/lock < <(lslocks -no PATH) –  Dec 02 '19 at 02:51
37

You can use the aptdcon command Manpage icon to queue up package manager tasks by communicating with aptdaemon instead of using apt-get directly.

So basically you can just do sudo aptdcon --install chromium-browser or whatever and while that command is running you can run it again but install different packages and apt-daemon will just queue them up instead of erroring out.

This is especially useful if you're doing a long upgrade or something and want to keep installing packages or if you're scripting something together and want to make sure installing things will be more reliable.

kiri
  • 28,246
  • 16
  • 81
  • 118
Jorge Castro
  • 71,754
  • 4
    Can aptdcon be run such that it accepts any prompts as apt-get -y does? – Noki Aug 16 '15 at 23:04
  • 4
    yes | aptdcon --hide-terminal --install "package" Hide Terminal is needed, else piping yes will cause issues. – whitehat101 Mar 17 '16 at 17:58
  • 2
    The problem with this answer is that it requires installing aptdcon. I'm working on a bootstrap script that does the initial setup of a VPS, where a background process is doing some initial setup as well. I can't install aptdcon until I can work around the lock. – Fake Name Oct 18 '16 at 04:14
  • 2
    @FakeName for initial server configuration you probably want to use cloud-init: http://cloudinit.readthedocs.io/en/latest/ – Jorge Castro Oct 18 '16 at 12:51
  • @JorgeCastro - My issue in this case is a VPS'es bringup script running in the background on newly deployed VPSes, which then collides with my own cloud setup system (using salt-stack). Adding /more/ dependencies isn't really the proper solution. – Fake Name Oct 18 '16 at 23:19
  • Basically, I'm having issues with the salt-stack deploy procedure fighting with Vultr's slightly strange deploy system. See https://github.com/saltstack/salt/issues/37062 Liberal use of wait-for-dpkg from the other answers appears to have solved the issue, with no install needed. – Fake Name Oct 18 '16 at 23:21
  • 1
    what package is aptdcon in? – ctrl-alt-delor Sep 08 '17 at 17:08
  • // , @ctrl-alt-delor https://askubuntu.com/q/1012576/150944 – Nathan Basanese Mar 07 '18 at 01:07
28

A very simple approach would be a script that waited for the lock to not be open. Let's call it waitforapt and stick it in /usr/local/bin:

#!/bin/sh

while sudo fuser /var/{lib/{dpkg,apt/lists},cache/apt/archives}/lock >/dev/null 2>&1; do
   sleep 1
done

Then just run sudo waitforapt && sudo apt-get install whatever. You could add exceptions into sudoers to allow you to run it without needing a password (you'll need it for the apt-get so it's no great gain).

Unfortunately this doesn't queue things. Given that some of apt's operations are interactive ("Are you sure you want to remove all those packages?!"), I can't see a good way around this...

Oli
  • 293,335
  • maybe -y to assume yes for all operations? – Braiam Nov 09 '13 at 13:36
  • Thank you. I think I can use it better if I use alias help. – Raja G Nov 09 '13 at 14:29
  • 3
    In not sure -y is desirable though. – Oli Nov 09 '13 at 19:02
  • 1
    I don't think you want to wrap sudo fuser in [[ $(...) ]]. It returns a zero exit code when the file is being accessed so that should be sufficient. – kiri Nov 10 '13 at 09:15
  • @Oli You could also do something like sudo waitforapt && xterm -e "sudo apt-get install whatever" to pop up a window when the lock is released. – gmatht May 26 '17 at 12:14
  • Better to use inotify. – ctrl-alt-delor Sep 08 '17 at 17:05
  • 5
    I found the following solution to be more complete, as there are several locks involved when working with apt-get:

    while sudo fuser /var/lib/dpkg/lock /var/lib/apt/lists/lock /var/cache/apt/archives/lock >/dev/null 2>&1; do echo 'Waiting for release of dpkg/apt locks'; sleep 5; done; sudo apt-get -yy update

    – Manuel Kießling Dec 07 '17 at 13:57
  • For my task (an Ansible run), I need to simply wait until apt is free, and then the further tasks are taken care of in another way. So I just grabbed the script and put it into my process as a one-liner, in place of where I'd have a simple wait. – snetch Sep 06 '19 at 16:00
20

Apart of the obvious &&, you may be looking for aptdcon. This tool is able to detect other instances of apt and wait them to finish:

sudo aptdcon --safe-upgrade
[/]  11% Waiting for other software managers to quit Waiting for aptitude

(I'm running aptitude somewhere else)

The advantage of this tool is that you can stock several actions consecutively without being worried of what you will be doing next. aptdcon is ideal for unattended scripts, and GUI installation, since you can allow the tool run in background as not to block your frontend.

The operations supported by aptdcon are:

  • --refresh, -c: This is the equivalent to apt-get update. It updates your package list.
  • --install, --remove, --upgrade, --purge, --downgrade. Each of them do as their names say. The name of the package(s) is mandatory. -i, -r, -u, -p: these are the short options for all except downgrade, who doesn't have one.
  • --safe-upgrade, --full-upgrade are the counterparts to apt-get's upgrade/dist-upgrade and aptitude's safe-upgrade/full-upgrade. These doesn't need parameters.
  • There are several others operations, which can be found in the manual. But, these are the most used by users interested in aptd. There are options that overlap with what apt-key, apt-cache, dpkg do.

apt-get itself doesn't support such methods (to wait for other instances of apt), so aptdcon is the preferred solution to GUI's package managers: USC uses aptd as back-end, same as Synaptic. Other solution is packagekit, but it doesn't support the function that you are looking for (yet).

Radu Rădeanu
  • 169,590
Braiam
  • 67,791
  • 32
  • 179
  • 269
  • 1
    aptdcon --install /path/to/pgk.deb Works like dpkg -i, although I wasn't able to find this explicitly mentioned in the manual. – whitehat101 Mar 17 '16 at 18:05
  • Can I use this in post-install script in a package ? – Nelson Teixeira Jan 03 '17 at 17:22
  • 1
    @NelsonTeixeira why would you use this in the ppst-install script? If post-install is being executed obviously apt is running. – Braiam Jan 03 '17 at 17:26
  • I want to make postinstall install some special packages that are not in the repository. Is this possible ? I would include them in package and then the script would install them after installation finishes. – Nelson Teixeira Jan 03 '17 at 17:32
  • @NelsonTeixeira again, why it would be necessary? The post-install scripts only runs when there's a package installation. I suggest you to ask a question instead and explain your case with more detail. – Braiam Jan 03 '17 at 17:56
8

Since 1.9.11 apt and apt-get have an option that lets you wait for the dpkg locks to be released.

Use the DPkg::Lock::Timeout option to set a timeout, in seconds, for an apt-get command. This example will wait for 60 seconds:

sudo apt-get -o DPkg::Lock::Timeout=60 install packagename

If you set that value to -1, it will keep waiting forever.

sudo apt-get -o DPkg::Lock::Timeout=-1 install packagename

For more information see: Waiting for apt locks without the hacky bash scripts. This option was added to apt-get in February 2020.

karel
  • 114,770
Mendhak
  • 4,440
  • The option works in Ubuntu 20.04 and later. The command generates one line of output for each second it waits unless you use -q=2 which implies -y i.e. no asking for user confirmation. – jarno Oct 26 '21 at 20:01
  • Still the problem is that you can not make sure the package database stays intact during you decide which packages to operate before the apt command is executed. – jarno Oct 27 '21 at 07:37
4

One-liner based on Oli's answer:

while sudo fuser /var/{lib/{dpkg,apt/lists},cache/apt/archives}/lock >/dev/null 2>&1; do sleep 1; done
Menasheh
  • 338
3

Unfortunately fuser doesn't do a lot for you when you are running in different unprivileged namespace containers like lxc.

Also, aptdcon is not installed by default (at least on 18.04) and backgrounds your task in a queue so you lose serialization. This isn't insurmountable, but it does mean your automation needs to have some way to avoid flock errors in apt when installing aptdcon, and you'll need to have some sort of wait loops for anything you need to serialize after installing packages via aptdcon unless there is some sort of flag for that already.

What does work is flock. This should also work over NFS etc as it uses file system locking in the same way apt does, only with the -w seconds parameter it will wait on your lock instead of throwing an error.

So following the wrapper model, add this as apt-get in /usr/local/bin/ and share away.

This also has the benefit of limiting IO by not allowing parallelism on apt so you can let cron trigger updates at midnight everywhere without beating up the disk.

#!/bin/bash
exec /usr/bin/flock -w 900 -F --verbose /var/cache/apt/archives/lock /usr/bin/apt-get $@  

A very nice and simple feature request for apt-get would be a -w flag to switch to a blocking / wait lock.

  • 1
    apt uses fcntl, not flock, so this won't work. https://askubuntu.com/questions/1077215/how-does-apt-get-use-var-cache-apt-archives-lock – Andy Feb 20 '19 at 23:21
  • This works perfectly and is a better option than using fuser as shown in many answers because fuser is still prone to race conditions between watching the lock and the target process grabbing the lock again. With flock the lock is acquired and then passed to the target process so it leaves no time for losing the lock in the meantime. – jrudolph Oct 17 '19 at 09:56
2

You could using a polling technique:

$ time (while ps -opid= -C apt-get > /dev/null; do sleep 1; done); \
  apt-get -y install some-other-package
malthe
  • 129
  • 2
2

I made a script which does this:

#!/bin/bash

# File path to watch
LOCK_FILE='/var/lib/dpkg/lock'

# tput escape codes
cr="$(tput cr)"
clr_end="$(tput el)"
up_line="$(tput cuu 1)"

CLEAN(){
    # Cleans the last two lines of terminal output,
    # returns the cursor to the start of the first line
    # and exits with the specified value if not False

    echo -n "$cr$clr_end"
    echo
    echo -n "$cr$clr_end$up_line"
    if [[ ! "$1" == "False" ]]; then
        exit $1
    fi
}

_get_cmdline(){
    # Takes the LOCKED variable, expected to be output from `lsof`,
    # then gets the PID and command line from `/proc/$pid/cmdline`.
    #
    # It sets `$open_program` to a user friendly string of the above.

    pid="${LOCKED#p}"
    pid=`echo $pid | sed 's/[\n\r ].*//'`
    cmdline=()
    while IFS= read -d '' -r arg; do
        cmdline+=("$arg")
    done < "/proc/${pid}/cmdline"
    open_program="$pid : ${cmdline[@]}"
}

# Default starting value
i=0

# Checks if the file is locked, writing output to $FUSER
while LOCKED="$(lsof -F p "$LOCK_FILE" 2>/dev/null)" ; do
    # This will be true if it isn't the first run
    if [[ "$i" != 0 ]]; then
        case $(($i % 4)) in
            0 ) s='-'
                i=4
                _get_cmdline # Re-checks the command line each 4th iteration
            ;;
            1 ) s=\\ ;;
            2 ) s='|' ;;
            3 ) s='/' ;;
        esac
    else
        # Traps to clean up the printed text and cursor position
        trap "CLEAN False; trap - SIGINT ; kill -SIGINT $$" SIGINT
        trap 'CLEAN $((128+15))' SIGTERM
        trap 'CLEAN $((128+1))' SIGHUP
        trap 'CLEAN $((128+3))' SIGQUIT

        # Default starting character
        s='-'

        _get_cmdline
        echo -n "$save_cur"
    fi
    # Prints the 2nd line first so the cursor is at the end of the 1st line (looks nicer)
    echo
    echo -n "$cr$clr_end$open_program"
    echo -n "$up_line$res_cur$cr$clr_end[$s] Waiting for other package managers to finish..."
    #echo -en "$cr$clr_end[$s] Waiting for other package managers to finish..."
    #echo -en "\n$cr$clr_end$open_program$cr$up_line"
    ((i++))
    sleep 0.025
done

CLEAN False

# This allows saving the script under a different name (e.g. `apt-wait`)
# and running it. It only imitates `apt-get` if it was launched as such
if [[ "${0##*/}" == 'apt-get' ]]; then
    exec /usr/bin/apt-get "$@"
    exit $?
fi

Save the above into /usr/local/sbin/apt-get. apt-get will then wait if another instance is already running.

Alternatively, save it as /usr/local/sbin/apt-wait, example usage:

apt-wait && aptitude

which will run aptitude after the current process holding the lock has exited.

Example run:

  1. First, an apt-get command is run, for example:

    $ sudo apt-get remove some_package
    
  2. Then, in another terminal, another command is run:

    $ sudo apt-get install some_other_package
    

    It will wait for the first command to finish then run. Output while waiting:

    [/] Waiting for other package managers to finish...
          28223 : /usr/bin/apt-get remove some_package
    
wjandrea
  • 14,236
  • 4
  • 48
  • 98
kiri
  • 28,246
  • 16
  • 81
  • 118