Sam & Max: Python, Django, Git et du cul » shell http://sametmax.com Deux développeurs en vadrouille qui se sortent les doigts du code Wed, 05 Feb 2014 14:20:37 +0000 en hourly 1 http://wordpress.org/?v=3.3.1 Profiter du notebook de IPython http://sametmax.com/profiter-du-notebook-de-ipython/ http://sametmax.com/profiter-du-notebook-de-ipython/#comments Wed, 23 Oct 2013 13:53:15 +0000 Sam http://sametmax.com/?p=7507 IPython est un des outils qui me font trouver la programmation Python plus agréable que dans tous les autres langages : un shell avec tellement d’astuces intégrées que ça donne envie de vomir des arc-en-ciel.

Aujourd’hui je vais vous parler d’une fonctionnalité fantastique est très peu connue de IPython : le notebook.

Si vous êtes scientifique ou manipulez pas mal de graphiques et de données numériques, c’est un must have. Mais personnellement je trouve que c’est aussi un fantastique outil d’éducation, c’est génial pour les cours, les démos, et expérimenter avec du code inconnu.

Ça se présente sous la forme d’un shell IPython intégré dans une page Web, mais que l’on peut avoir sur son desktop, pas besoin d’avoir un serveur distant. Si vous avez un peu de temps, voici une vidéo de démo (avec un musique horrible) :

L’installation sous Ubuntu, c’est juste :

sudo apt-get install ipython-notebook

Pour Windows, il me semble, de mémoire, que c’est intégré dans l’exe. Pour Mac, je ne sais pas.

Et derrière il suffit de lancer la commande :

ipython notebook

Et il va vous ouvrir votre navigateur avec un onglet sur votre notebook.

L’intérêt principal du notebook est que TOUT le code est coloré et est modifiable comme sur dans fichier mais TOUT le code est exécutable (et affiche son résultat juste après chaque ligne) comme dans un shell iPython, avec completion du code. Le meilleur des deux mondes quoi.

En prime on peut embeder du markdown (et donc des liens, des images, etc) dans le même notebook, sauvegarder ça pour plus tard, le partager, etc.

Personnellement, j’ai ceci dans mon .bashrc :

notebook () {
    processes=$(ps aux | grep -i -P "ipython notebook" | wc -l)
    if [[ $processes -lt 2 ]]
    then
        pushd /tmp
        nohup ipython notebook --port 8889 $1 &
        popd
    else
        firefox http://127.0.0.1:8889
    fi
}

Comme ça en une commande je lance le serveur du notebook sur le port que je veux si il ne tourne pas, et si il tourne, j’ouvre firefox et ouvre un onglet dessus.

Quelques autres options très utiles :

–pylab : Charger plein de libs pour transformer IPython en un matlab killer.
–gui=lib : permet d’intégrer l’event loop de QT, wx, gtk, etc.
–ip=0.0.0.0 : si on veut que d’autres puisse accéder à notre notebook.

Il existe un espèce de pastebin spécialement fait pour le notebook, qui permet de partager ses expérimentations.

flattr this!

]]>
http://sametmax.com/profiter-du-notebook-de-ipython/feed/ 21
Nouvelle config iPython http://sametmax.com/nouvelle-config-ipython/ http://sametmax.com/nouvelle-config-ipython/#comments Sun, 19 May 2013 17:24:44 +0000 Sam http://sametmax.com/?p=6158 customiser ce shell. Voici ce que j'ai dans mon ./.config/ipython/profile_default/ipython_config.py...]]> J’ai bricolé une config pour iPython dernièrement. Rappelez-vous, on peut complètement customiser ce shell.

Voici ce que j’ai dans mon ./.config/ipython/profile_default/ipython_config.py:

c.TerminalIPythonApp.exec_lines = [
'doctest_mode on',
'from __future__ import unicode_literals, absolute_import, division',
'import sys',
'import os',
'import re',
'import json',
'import base64',
'import calendar',
'import csv',
'import datetime',
'import itertools',
'import random',
'import hashlib',
'import tempfile',
'import argparse',
'import math',
'import random',
'import subprocess',
'from glob import glob',
'from uuid import uuid4',
'from datetime import datetime, timedelta',
'from collections import Counter, OrderedDict',
"""
def initial_imports():
 
    # ne faites pas ça dans un code de prod :-)
    global path, relativedelta, requests 
 
    print("\\nImported : 'sys', 'os', 're', 'json', 'base64', 'calendar', 'csv', 'datetime', 'itertools', 'random', 'hashlib', 'tempfile', 'argparse', 'math', 'random', 'glob', 'uuid4', 'datetime', 'timedelta', 'Counter', 'OrderedDict', 'subprocess'\\n")
 
    try:
        from path import path
        print('Imported : "path"')
    except ImportError:
        print("'path' is not available")
 
    try:
        from dateutil import relativedelta
        print('Imported : "relativedelta"')
    except ImportError:
        print("'dateutil' is not available")
 
    try:
        import requests
        print('Imported : "requests"')
    except ImportError:
        print("'requests' is not available")
 
    try:
        env = os.environ['VIRTUAL_ENV']
        print("\\nENV '{}'. You can import:\\n".format(os.path.basename(env)))
        cmd = subprocess.check_output([env + "/bin/pip", "freeze"],
                                      stderr=subprocess.STDOUT).strip().split("\\n")
        p = re.compile(r'(^.*\:\s)|((#|@).*$)|(==.*$)')
        print("'" + "', '".join(sorted(set(os.path.basename(p.sub('', f)) for f in cmd))) + "'")
    except KeyError:
        pass
 
    print('')
""",
"initial_imports()"
]

Du coup, au démarrage d’iPython j’ai les bénéfices suivant :

  • Comportement de Python 3 pour l’unicode, les imports et la division. Ils sont plus sains et facile à manipuler (plus besoin de préfixer de ‘u’ les chaînes par exemple).
  • J’ai tout de suite sous la main une tonne de modules que j’utilise souvent sans avoir à les importer (os, datetime, random, etc).
  • J’importe aussi des modules optionnels (path, requests…).
  • Je sais si je suis dans un environnement virtuel, et si oui, ce qu’il y a installé dedans.

Je vire aussi la bannière de démarrage :

c.TerminalIPythonApp.display_banner = False

Et hop, mon affichage au démarrage ressemble à ça :

$ ipython
Exception reporting mode: Plain
Doctest mode is: ON

Imported : 'sys', 'os', 're', 'json', 'base64', 'calendar', 'csv', 'datetime', 'itertools', 'random', 'hashlib', 'tempfile', 'argparse', 'math', 'random', 'glob', 'uuid4', 'datetime', 'timedelta', 'Counter', 'OrderedDict', 'subprocess'

'path' is not available
'dateutil' is not available
Imported : "requests"

ENV 'my_env'. You can import:

'BeautifulSoup', 'Django', 'Fabric', 'Jinja2', 'Pillow', 'Pygments', 'Sphinx', 'URLObject', 'Werkzeug', 'argparse', 'beautifulsoup4', 'colorlog', 'distribute', 'django-appconf', 'django-bootstrap-pagination', 'django-bootstrap-toolkit', 'django-compressor', 'django-debug-toolbar', 'django-extensions', 'django-guardian', 'django-loginas', 'django-model-utils', 'django-pdb', 'django-pytest', 'django-rest-framework.git', 'django-template-repl', 'docutils', 'easy-thumbnails', 'feedparser', 'gunicor

Ça fait un gros bloc visuellement, mais ça ne s’affiche qu’une fois, et le délai est acceptable.

Si j’ai besoin de l’info à nouveau, je peux toujours appeler :

initial_imports()

Que ce genre de choses soient possibles et faciles me font adorer Python. La force du langage, ce n’est pas seulement son core, c’est tout son environnement, ses libs, ses outils qui gravitent autour qui rendent l’expérience de dev fantastique.

flattr this!

]]>
http://sametmax.com/nouvelle-config-ipython/feed/ 9
A l’intérieur de mon .bashrc http://sametmax.com/a-linterieur-de-mon-bashrc/ http://sametmax.com/a-linterieur-de-mon-bashrc/#comments Tue, 22 Jan 2013 10:26:44 +0000 Sam http://sametmax.com/?p=4238 .bashrc donne rapidement lieu dans les comments à un concours de celui qui a la plus longue (jusqu'à ce qu'arrive un utilisateur de zsh, et alors commence le concours de celui qui pisse le plus loin). Mais bon, aujourd'hui j'ai la flemme d'écrire un article complet.]]> Généralement montrer son .bashrc donne rapidement lieu dans les comments à un concours de celui qui a la plus longue (jusqu’à ce qu’arrive un utilisateur de zsh, et alors commence le concours de celui qui pisse le plus loin). Mais bon, aujourd’hui j’ai la flemme d’écrire un article complet.

Rappel : le .bashrc c’est le fichier qui est automatiquement chargé au démarrage du shell bash, le shell par défaut sur la plupart des Unix incluant Mac et Ubuntu.

Donc si vous voulez avoir une expérience de travail personnalisée dans votre terminal, c’est dans ce fichier que ça se passe.

Personnellement, j’ai tout le bordel que met Ubuntu par défaut (notamment les trucs avec les autocomplétions de partout). En en prime je rajoute :

# un raccourcis pour lancer redis car je le met sur off par défaut met je le 
# lance souvent
 
function redis {
    if [ ! -e "/var/run/redis/redis-server.pid" ] ; then
        sudo service redis-server start
    fi
}
 
# un tas de raccourcis type
 
function projet_django {
    workon projet_django;
    cd /home/sam/Work/projet_django/repo/projet_django;
 
    if [[ $# -ne 0 ]]; then
        ./manage.py $@
    fi
}
alias sb='projet_django'
 
# CF: http://sametmax.com/un-alias-bash-pour-django-virtualenv-dont-je-ne-peux-plus-me-passer/
 
 
# fonction fétiche de bourrin
 
function killbill {
    BAK=$IFS
    IFS=$'\n'
    for id in $(ps aux | grep -P -i $1 | grep -v "grep" | awk '{printf $2" "; for (i=11; i<NF; i++) printf $i" "; print $NF}'); do 
        service=$(echo $id | cut -d " " -f 1)
        if [[ $2 == "-t" ]]; then
            echo $service \"$(echo $id | cut -d " " -f 2-)\" "would be killed"
        else
 
            echo $service \"$(echo $id | cut -d " " -f 2-)\" "killed"
            kill -9 $service
        fi
    done
    IFS=$BAK
}
 
# que j'ai un peu amélioré depuis http://sametmax.com/tu-vas-crever-oui/
 
 
# une fonction pour commiter rapidement 
function commit {
    git commit -m "`echo "$*" | sed -e 's/^./\U&\E/g'`"
}
alias co=commit;
 
# en gros on tappe "co message de commit". Sans guillement, sans rien.
 
# des completions pour manage.py et git
 
source /home/sam/.django_bash_completion
source /home/sam/.git-completion.bash
 
# activation du cache de pip pour éviter de télécharger 20 fois la même lib
 
PIP_DOWNLOAD_CACHE='/opt/pip-cache';
export PIP_DOWNLOAD_CACHE;
 
# un super outil pour un git plus user friendly
 
[ -s "/home/sam/.scm_breeze/scm_breeze.sh" ] && source "/home/sam/.scm_breeze/scm_breeze.sh"
alias c="git_index"
 
# j'en avais parlé ici : http://sametmax.com/scm-breeze-facilite-la-selection-de-vos-fichiers-a-commiter-sous-git-avec/
 
 
# un prompt personalisé tout en couleur
# formaté comme ça: [nom du virtualenv] sam ~/path (branche git) $
 
source ~/.prompt.bash
 
# le fichier ressemble à ça : http://0bin.net/paste/d103a91dc41818462da0c8468ddddf4141f2efd6#dKr6qWHfAVZsSnOQ7fA4pPQ3Dquhz0Oj1b2OF8xw6vA=
 
# la configuration de virtualenv wrapper 
export WORKON_HOME=/home/sam/.virtualenvs
mkdir -p $WORKON_HOME
source /usr/local/bin/virtualenvwrapper.sh
 
# plus de détails ici : http://sametmax.com/les-environnement-virtuels-python-virtualenv-et-virtualenvwrapper/
 
 
# j'ai récément succombé aux sirène du virtualenv automatique
# j'avais beaucoup d'a priori (particulirement écraser la commande CD)
# mais à l'usage, c'est pratique
has_virtualenv() {
    if [ -e .venv ]; then
        workon `cat .venv`
    fi
}
venv_cd () {
    builtin cd "$@" && has_virtualenv
}
alias cd="venv_cd"
has_virtualenv
 
# en gros ça m'active un virtualenv si il y a un fichier .venv avec un nom
# de virtualenv dedans dans le dossier courrant
 
 
# je vous en parlais récement (http://sametmax.com/configurer-votre-terminal-pour-quil-vous-notifie-de-la-fin-dune-commande-longue/)
# c'est pour avoir une notifi à la fin des commandes longues 
notify_when_long_running_commands_finish_install
 
 
# ca c'est un truc de feignasse car je me souviens jamais comment extraire 
# en ligne de commande tel ou tel format
 
extract () {
    if [ -f $1 ]
    then
        case $1 in
            (*.7z) 7z x $1 ;;
            (*.lzma) unlzma $1 ;;
            (*.rar) unrar x $1 ;;
            (*.tar) tar xvf $1 ;;
            (*.tar.bz2) tar xvjf $1 ;;
            (*.bz2) bunzip2 $1 ;;
            (*.tar.gz) tar xvzf $1 ;;
            (*.gz) gunzip $1 ;;
            (*.tar.xz) tar Jxvf $1 ;;
            (*.xz) xz -d $1 ;;
            (*.tbz2) tar xvjf $1 ;;
            (*.tgz) tar xvzf $1 ;;
            (*.zip) unzip $1 ;;
            (*.Z) uncompress ;;
            (*) echo "don't know how to extract '$1'..." ;;
        esac
    else
        echo "Error: '$1' is not a valid file!"
        exit 0
    fi
}
 
# ça date de http://sametmax.com/decompresser-sous-linux-en-ligne-de-commande/
 
# et quelques alias pour des tâches de tous les jours
 
alias ..='cd ..'
alias ...='cd ../../'
alias ....='cd ../../../'
alias .....='cd ../../../../'
alias ......='cd ../../../../../'
 
alias process='ps aux | grep'
alias serve="python -c 'import SimpleHTTPServer; SimpleHTTPServer.test()'"

flattr this!

]]>
http://sametmax.com/a-linterieur-de-mon-bashrc/feed/ 20
Quelques bonnes raisons de plus d’utiliser iPython http://sametmax.com/quelques-bonnes-raisons-de-plus-dutiliser-ipython/ http://sametmax.com/quelques-bonnes-raisons-de-plus-dutiliser-ipython/#comments Sun, 23 Dec 2012 18:01:36 +0000 Sam http://sametmax.com/?p=3644 iPython, ola, iPython, il déchire sa génitrice avec une poutrelle en verre pilé.]]> Le shell Python est vraiment pratique pour expérimenter, apprendre le langage, tester un snippet vitos ou administrer son site à distance. Mais iPython, ola, iPython, il déchire sa génitrice avec une poutrelle en verre pilé.

Voici quelques commandes qui vous donneront moult raisons de plus pour très vite installer ce shell alternatif.

Autocall

Taper des parenthèses et des guillemets, c’est sooooooo 1995. iPython peut vous les rajouter automatiquement dans les appels de fonctions, il faut juste préfixer de “/”, “,” ou “;” votre appel de fonction pour qu’il fasse la conversion:

/f 1,2 => f(1,2)
,f 1 2 => f("1","2")
;f 1 2 => f("1 2")

Accès au shell système

Préfixez votre commande d’un “!” bien couillu, et iPython va vous exécuter ça dans le shell système (par exemple bash) et vous retourner le résulat:

>>> ! cat /etc/fstab | grep 'ext4'
UUID=e33c5b98-1570-44d6-a32f-5e7970e1e588 /               ext4    errors=remount-ro 0       1

Oui, oui, on est dans un shell Python là. Ou comment bien faire chier la coloration syntaxique de votre blog.

Et on peut mettre tout ça dans une variable et le traiter en Python derrière:

>>> files = !ls
>>> [f.upper() for f in files]
['BUREAU', 'DOCUMENTS', 'EXAMPLES.DESKTOP', 'IMAGES', 'MOD\xc3\xa8LES', 'MUSIQUE', 'PUBLIC', 'T\xc3\xa9L\xc3\xa9CHARGEMENTS', 'UBUNTU ONE', 'VID\xc3\xa9OS',]

A noter que tout est exécuté dans un autre process, ainsi !cd n’aura aucun effet. Mais tout est prévu: %cd et %pwd proxient tout ça vers os.chdir et os.getcwd :-)

Debugger

Entrez %timeit expression pour qu’il qu’iPython vous l’exécute 10000000 fois et vous donne sa performance.

>>> %timeit x=2**100
10000000 loops, best of 3: 22.5 ns per loop

Autre commande: %pdb vous lancera automatiquement votre débuggeur favoris si une exception se lève durant votre session.

Se faciliter la vie

%edit ouvre un editeur (par défault VI sous nunux, mais c’est configurable), et si vous sauvegardez, le contenu tapé est récupéré et exécuté par iPython. Génial pour les bouts de code trop relou à taper sur le prompt.

%gui qt|wx|gtk lance l’intégration de la main loop d’un des toolkits graphiques afin pour de pouvoir faire mumuse avec des widgets sans bloquer votre shell.

Vous étiez vous demandé pourquoi le prompt iPython était plein de In et de Out, et pas de >>> ? Parce que tout l’historique est numéroté, et accessible:

In [38]: 1 + 1
Out[38]: 2
 
In [39]: In[38]
Out[39]: u'1 + 1'
 
In [40]: Out[38]
Out[40]: 2

Mais vous pouvez aussi utiliser %doctest_mode qui donne à votre prompt un air de shell Python normal. L’avantage ? On peut copier le contenu d’un autre shell dedans (et donc d’une doctest) avec les chevrons !

In [49]: %doctest_mode
Exception reporting mode: Plain
Doctest mode is: ON
>>> >>> for pom in ('pom', 'pom', 'pom', 'pom'):
...     ...     print pom
...     ... 
pom
pom
pom
pom

flattr this!

]]>
http://sametmax.com/quelques-bonnes-raisons-de-plus-dutiliser-ipython/feed/ 7
Personnalisez le démarrage d’iPython http://sametmax.com/personnalisez-le-demarrage-dipython/ http://sametmax.com/personnalisez-le-demarrage-dipython/#comments Sun, 16 Dec 2012 13:14:54 +0000 Sam http://sametmax.com/?p=3643 iPython, c’est bon. Et ça peut être encore meilleur.

Avant on ajoutait un peu de sel à notre super shell en éditant les fichiers .ipython/ipythonrc et .ipython/ipy_user_conf.py. Mais ça c’était avant.

Maintenant on fait :

$ ipython profile create

Ce qui va créer un fichier ~/.config/ipython/profile_default/ipython_config.py (et ipython_qtconsole_config.py si vous avez installé la version qt).

Il ne vous reste plus qu’à éditer ce fichier pour personnaliser le démarrage de iPython.

Executer du code au démarrage

N’importe quelle ligne de code Python ou de commande magique (les trucs qui commencent pas ‘%’ qui ne fonctionnent que dans iPython) !

Très utile pour les imports par exemple. Voici ce que je fais moi au démarrage :

c.TerminalIPythonApp.exec_lines = [
'%doctest_mode',
'import os, sys, re, json',
'from datetime import datetime, timedelta',
'''
try:
    from path import path
except ImportError:
    pass
''',
'''
try:
    import requests
except ImportError:
    pass
''',
'''
try:
    from batbelt.structs import *
    from batbelt.objects import attr
    from batbelt.strings import slugify, normalize, json_dumps, json_loads
except ImportError:
    pass
'''
]

Du coup j’ai quasiment jamais besoin d’importer un truc, car ce que j’utilise le plus est déjà là. %doctest_mode me permet de coller des docstrings sans me fouler.

Programmation graphique

iPython possède une boucle principale. Les toolkits graphiques aussi. Si vous faites vos essais du code du second dans le premier, ça va bloquer. Heureusement on peut demander à iPython d’intégrer la main loop d’un des toolkits graphiques les plus célèbres en choisissant parmi : ‘qt’, ‘wx’, ‘gtk’, ‘glut’, ‘pyglet’ et ‘osx’ et en settant :

c.TerminalIPythonApp.gui = 'qt'

Virer le header

Quand on lance le shell, il vous raconte sa vie. Pour lui dire de fermer sa gueule :

c.TerminalIPythonApp.display_banner = False

Lancer un script complet

Bon, ça c’est si vous avez de gros besoins…

c.TerminalIPythonApp.file_to_run = '/path/to/script.py'

Sauter la confirmation à la fermeture

Oui, je suis sûr te vouloir te fermer connard ! Tu crois que j’ai appuyé sur Ctrl + d par erreur en éternuant ?

c.TerminalInteractiveShell.confirm_exit = False

Choisir son éditeur externe

Si vous tapez %edit dans iPython, il ouvre un éditeur. Vous tapez votre code, vous sauvegardez, vous fermez. Et iPython récupère le code et l’éxécute. Par défault il utilise VI. On peut faire mieux.

c.TerminalInteractiveShell.editor = '/chemin/vers/sublime-text -w'

flattr this!

]]>
http://sametmax.com/personnalisez-le-demarrage-dipython/feed/ 8
Le _ (underscore) dans le shell interactif Python http://sametmax.com/le-_-underscode-dans-le-shell-interactif-python/ http://sametmax.com/le-_-underscode-dans-le-shell-interactif-python/#comments Thu, 20 Sep 2012 19:51:30 +0000 Sam http://sametmax.com/?p=1186 C’est une fonctionnalité que peu de gens connaissent, pourtant très utile.

Dans le shell Python, à chaque fois qu’on évalue une expression, le résultat affiché est référencé dans la variable _.

>>> 1 + 1
2
>>> print _
2

On peut évidement la réutiliser comme n’importe quelle variable:

>>> range(5)
[0, 1, 2, 3, 4]
>>> [x * 2 for x in _]
[0, 2, 4, 6, 8]

Pour éviter d’effacer la variable trop souvent, quand le résultat retourné est None, _ ne change pas, donc il ne change pas quand on print :-)

>>> 1 > 2 or None
>>> _
[0, 2, 4, 6, 8]
>>> print 'a'
a
>>> _
[0, 2, 4, 6, 8]

Ça ne marche que dans le shell, pas dans un script.

flattr this!

]]>
http://sametmax.com/le-_-underscode-dans-le-shell-interactif-python/feed/ 12
Etre prévenu quand une longue commande a fini son éxécution sous Ubuntu et Mac http://sametmax.com/etre-prevenu-quand-une-longue-commande-a-fini-son-execution-sous-ubuntu-et-mac/ http://sametmax.com/etre-prevenu-quand-une-longue-commande-a-fini-son-execution-sous-ubuntu-et-mac/#comments Mon, 18 Jun 2012 18:19:24 +0000 Sam http://sametmax.com/?p=947 Quand on lance une commande qui tourne pendant un certain bout de temps (typiquement une migration ou un encodage en masse), on a pas envie d’avoir les yeux vissés sur le terminal pour savoir quand elle se termine.

Alertes sous Ubuntu

Entrez simplement:

alert "Encore merci et bonne chance, nous sommes avec vous."

Ceci va faire apparaitre une notification avec le message passé en paramètre.

Pour avoir l’alerte lancée une fois une commande terminée, il suffit de la mettre just après, derrière un point virgule:

sleep 5; alert

Le message sera alors la commande précédente.

Pour ceux qui ne sont pas sous Ubuntu, on peut créer la commande manuellement (Ubuntu ne fait que mettre cette ligne par défaut dans le .bashrc):

alias alert='notify-send --urgency=low -i "$([ $? = 0 ] && echo terminal || echo error)" "$(history|tail -n1|sed -e '\''s/^\s*[0-9]\+\s*//;s/[;&|]\s*alert$//'\'')"'

Il faut bien entendu s’assurer que la commande “notify-send” soit installée.

Alertes sous Mac

Mac vient avec un synthétiseur vocal qui peut être utilisé de manière similaire:

say "I just want to thank you both, we're all counting on you."

Le Mac va alors vous lire la phrase à haute, hum… voix. Il faut bien sûr s’assurer que l’on a le volume réglé en conséquence sinon l’alerte risque de ne pas se passer comme prévu :-)

Un bon truc donc pour foutre la honte à votre collègue de bureau, mettez le sond à fond et lancez juste avant la pause déjeûner dans un onglet discretos:

sleep 5000; say "Oh yeah bitch, just like that, just like that. Suck it ! Suck it hard. Oh Yeaaaaaaaaaaaaaa!"

C’est particulièrement perturbant sur une voix de synthèse.

Cette commande fonctionne mieux avec des phrases en anglais.

Attention cependant, ces deux astuces ne fonctionnent qu’en local, puisse qu’un ordinateur distant n’a pas accès au système de notification de votre desktop. Un défaut ennuyeux car on lance souvent des commandes longues via SSH.

flattr this!

]]>
http://sametmax.com/etre-prevenu-quand-une-longue-commande-a-fini-son-execution-sous-ubuntu-et-mac/feed/ 6