Format: text/x-python
⬇ Download File (30.0 KB)
   1  """Implementation of magic functions for interaction with the OS.
   2  
   3  Note: this module is named 'osm' instead of 'os' to avoid a collision with the
   4  builtin.
   5  """
   6  # Copyright (c) IPython Development Team.
   7  # Distributed under the terms of the Modified BSD License.
   8  
   9  import io
  10  import os
  11  import pathlib
  12  import re
  13  import sys
  14  from pprint import pformat
  15  
  16  from IPython.core import magic_arguments
  17  from IPython.core import oinspect
  18  from IPython.core import page
  19  from IPython.core.alias import AliasError, Alias
  20  from IPython.core.error import UsageError
  21  from IPython.core.magic import  (
  22      Magics, compress_dhist, magics_class, line_magic, cell_magic, line_cell_magic
  23  )
  24  from IPython.testing.skipdoctest import skip_doctest
  25  from IPython.utils.openpy import source_to_unicode
  26  from IPython.utils.process import abbrev_cwd
  27  from IPython.utils.terminal import set_term_title
  28  from traitlets import Bool
  29  from warnings import warn
  30  
  31  
  32  @magics_class
  33  class OSMagics(Magics):
  34      """Magics to interact with the underlying OS (shell-type functionality).
  35      """
  36  
  37      cd_force_quiet = Bool(False,
  38          help="Force %cd magic to be quiet even if -q is not passed."
  39      ).tag(config=True)
  40  
  41      def __init__(self, shell=None, **kwargs):
  42  
  43          # Now define isexec in a cross platform manner.
  44          self.is_posix = False
  45          self.execre = None
  46          if os.name == 'posix':
  47              self.is_posix = True
  48          else:
  49              try:
  50                  winext = os.environ['pathext'].replace(';','|').replace('.','')
  51              except KeyError:
  52                  winext = 'exe|com|bat|py'
  53              try:
  54                  self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
  55              except re.error:
  56                  warn("Seems like your pathext environmental "
  57                       "variable is malformed. Please check it to "
  58                       "enable a proper handle of file extensions "
  59                       "managed for your system")
  60                  winext = 'exe|com|bat|py'
  61                  self.execre = re.compile(r'(.*)\.(%s)$' % winext,re.IGNORECASE)
  62  
  63          # call up the chain
  64          super().__init__(shell=shell, **kwargs)
  65  
  66  
  67      def _isexec_POSIX(self, file):
  68          """
  69          Test for executable on a POSIX system
  70          """
  71          if os.access(file.path, os.X_OK):
  72              # will fail on maxOS if access is not X_OK
  73              return file.is_file()
  74          return False
  75  
  76  
  77      
  78      def _isexec_WIN(self, file):
  79          """
  80          Test for executable file on non POSIX system
  81          """
  82          return file.is_file() and self.execre.match(file.name) is not None
  83  
  84      def isexec(self, file):
  85          """
  86          Test for executable file on non POSIX system
  87          """
  88          if self.is_posix:
  89              return self._isexec_POSIX(file)
  90          else:
  91              return self._isexec_WIN(file)
  92  
  93  
  94      @skip_doctest
  95      @line_magic
  96      def alias(self, parameter_s=''):
  97          """Define an alias for a system command.
  98  
  99          '%alias alias_name cmd' defines 'alias_name' as an alias for 'cmd'
 100  
 101          Then, typing 'alias_name params' will execute the system command 'cmd
 102          params' (from your underlying operating system).
 103  
 104          Aliases have lower precedence than magic functions and Python normal
 105          variables, so if 'foo' is both a Python variable and an alias, the
 106          alias can not be executed until 'del foo' removes the Python variable.
 107  
 108          You can use the %l specifier in an alias definition to represent the
 109          whole line when the alias is called.  For example::
 110  
 111            In [2]: alias bracket echo "Input in brackets: <%l>"
 112            In [3]: bracket hello world
 113            Input in brackets: <hello world>
 114  
 115          You can also define aliases with parameters using %s specifiers (one
 116          per parameter)::
 117  
 118            In [1]: alias parts echo first %s second %s
 119            In [2]: %parts A B
 120            first A second B
 121            In [3]: %parts A
 122            Incorrect number of arguments: 2 expected.
 123            parts is an alias to: 'echo first %s second %s'
 124  
 125          Note that %l and %s are mutually exclusive.  You can only use one or
 126          the other in your aliases.
 127  
 128          Aliases expand Python variables just like system calls using ! or !!
 129          do: all expressions prefixed with '$' get expanded.  For details of
 130          the semantic rules, see PEP-215:
 131          https://peps.python.org/pep-0215/.  This is the library used by
 132          IPython for variable expansion.  If you want to access a true shell
 133          variable, an extra $ is necessary to prevent its expansion by
 134          IPython::
 135  
 136            In [6]: alias show echo
 137            In [7]: PATH='A Python string'
 138            In [8]: show $PATH
 139            A Python string
 140            In [9]: show $$PATH
 141            /usr/local/lf9560/bin:/usr/local/intel/compiler70/ia32/bin:...
 142  
 143          You can use the alias facility to access all of $PATH.  See the %rehashx
 144          function, which automatically creates aliases for the contents of your
 145          $PATH.
 146  
 147          If called with no parameters, %alias prints the current alias table
 148          for your system.  For posix systems, the default aliases are 'cat',
 149          'cp', 'mv', 'rm', 'rmdir', and 'mkdir', and other platform-specific
 150          aliases are added.  For windows-based systems, the default aliases are
 151          'copy', 'ddir', 'echo', 'ls', 'ldir', 'mkdir', 'ren', and 'rmdir'.
 152  
 153          You can see the definition of alias by adding a question mark in the
 154          end::
 155  
 156            In [1]: cat?
 157            Repr: <alias cat for 'cat'>"""
 158  
 159          par = parameter_s.strip()
 160          if not par:
 161              aliases = sorted(self.shell.alias_manager.aliases)
 162              # stored = self.shell.db.get('stored_aliases', {} )
 163              # for k, v in stored:
 164              #     atab.append(k, v[0])
 165  
 166              print("Total number of aliases:", len(aliases))
 167              sys.stdout.flush()
 168              return aliases
 169  
 170          # Now try to define a new one
 171          try:
 172              alias,cmd = par.split(None, 1)
 173          except TypeError:
 174              print(oinspect.getdoc(self.alias))
 175              return
 176          
 177          try:
 178              self.shell.alias_manager.define_alias(alias, cmd)
 179          except AliasError as e:
 180              print(e)
 181      # end magic_alias
 182  
 183      @line_magic
 184      def unalias(self, parameter_s=''):
 185          """Remove an alias"""
 186  
 187          aname = parameter_s.strip()
 188          try:
 189              self.shell.alias_manager.undefine_alias(aname)
 190          except ValueError as e:
 191              print(e)
 192              return
 193          
 194          stored = self.shell.db.get('stored_aliases', {} )
 195          if aname in stored:
 196              print("Removing %stored alias",aname)
 197              del stored[aname]
 198              self.shell.db['stored_aliases'] = stored
 199  
 200      @line_magic
 201      def rehashx(self, parameter_s=''):
 202          """Update the alias table with all executable files in $PATH.
 203  
 204          rehashx explicitly checks that every entry in $PATH is a file
 205          with execute access (os.X_OK).
 206  
 207          Under Windows, it checks executability as a match against a
 208          '|'-separated string of extensions, stored in the IPython config
 209          variable win_exec_ext.  This defaults to 'exe|com|bat'.
 210  
 211          This function also resets the root module cache of module completer,
 212          used on slow filesystems.
 213          """
 214          from IPython.core.alias import InvalidAliasError
 215  
 216          # for the benefit of module completer in ipy_completers.py
 217          del self.shell.db['rootmodules_cache']
 218  
 219          path = [os.path.abspath(os.path.expanduser(p)) for p in
 220              os.environ.get('PATH','').split(os.pathsep)]
 221  
 222          syscmdlist = []
 223          savedir = os.getcwd()
 224  
 225          # Now walk the paths looking for executables to alias.
 226          try:
 227              # write the whole loop for posix/Windows so we don't have an if in
 228              # the innermost part
 229              if self.is_posix:
 230                  for pdir in path:
 231                      try:
 232                          os.chdir(pdir)
 233                      except OSError:
 234                          continue
 235  
 236                      # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
 237                      dirlist = os.scandir(path=pdir)
 238                      for ff in dirlist:
 239                          if self.isexec(ff):
 240                              fname = ff.name
 241                              try:
 242                                  # Removes dots from the name since ipython
 243                                  # will assume names with dots to be python.
 244                                  if not self.shell.alias_manager.is_alias(fname):
 245                                      self.shell.alias_manager.define_alias(
 246                                          fname.replace('.',''), fname)
 247                              except InvalidAliasError:
 248                                  pass
 249                              else:
 250                                  syscmdlist.append(fname)
 251              else:
 252                  no_alias = Alias.blacklist
 253                  for pdir in path:
 254                      try:
 255                          os.chdir(pdir)
 256                      except OSError:
 257                          continue
 258  
 259                      # for python 3.6+ rewrite to: with os.scandir(pdir) as dirlist:
 260                      dirlist = os.scandir(pdir)
 261                      for ff in dirlist:
 262                          fname = ff.name
 263                          base, ext = os.path.splitext(fname)
 264                          if self.isexec(ff) and base.lower() not in no_alias:
 265                              if ext.lower() == '.exe':
 266                                  fname = base
 267                                  try:
 268                                      # Removes dots from the name since ipython
 269                                      # will assume names with dots to be python.
 270                                      self.shell.alias_manager.define_alias(
 271                                          base.lower().replace('.',''), fname)
 272                                  except InvalidAliasError:
 273                                      pass
 274                                  syscmdlist.append(fname)
 275  
 276              self.shell.db['syscmdlist'] = syscmdlist
 277          finally:
 278              os.chdir(savedir)
 279  
 280      @skip_doctest
 281      @line_magic
 282      def pwd(self, parameter_s=''):
 283          """Return the current working directory path.
 284  
 285          Examples
 286          --------
 287          ::
 288  
 289            In [9]: pwd
 290            Out[9]: '/home/tsuser/sprint/ipython'
 291          """
 292          try:
 293              return os.getcwd()
 294          except FileNotFoundError as e:
 295              raise UsageError("CWD no longer exists - please use %cd to change directory.") from e
 296  
 297      @skip_doctest
 298      @line_magic
 299      def cd(self, parameter_s=''):
 300          """Change the current working directory.
 301  
 302          This command automatically maintains an internal list of directories
 303          you visit during your IPython session, in the variable ``_dh``. The
 304          command :magic:`%dhist` shows this history nicely formatted. You can
 305          also do ``cd -<tab>`` to see directory history conveniently.
 306          Usage:
 307  
 308            - ``cd 'dir'``: changes to directory 'dir'.
 309            - ``cd -``: changes to the last visited directory.
 310            - ``cd -<n>``: changes to the n-th directory in the directory history.
 311            - ``cd --foo``: change to directory that matches 'foo' in history
 312            - ``cd -b <bookmark_name>``: jump to a bookmark set by %bookmark
 313            - Hitting a tab key after ``cd -b`` allows you to tab-complete
 314              bookmark names.
 315  
 316            .. note::
 317              ``cd <bookmark_name>`` is enough if there is no directory
 318              ``<bookmark_name>``, but a bookmark with the name exists.
 319  
 320          Options:
 321  
 322          -q               Be quiet. Do not print the working directory after the
 323                           cd command is executed. By default IPython's cd
 324                           command does print this directory, since the default
 325                           prompts do not display path information.
 326  
 327          .. note::
 328             Note that ``!cd`` doesn't work for this purpose because the shell
 329             where ``!command`` runs is immediately discarded after executing
 330             'command'.
 331  
 332          Examples
 333          --------
 334          ::
 335  
 336            In [10]: cd parent/child
 337            /home/tsuser/parent/child
 338          """
 339  
 340          try:
 341              oldcwd = os.getcwd()
 342          except FileNotFoundError:
 343              # Happens if the CWD has been deleted.
 344              oldcwd = None
 345  
 346          numcd = re.match(r'(-)(\d+)$',parameter_s)
 347          # jump in directory history by number
 348          if numcd:
 349              nn = int(numcd.group(2))
 350              try:
 351                  ps = self.shell.user_ns['_dh'][nn]
 352              except IndexError:
 353                  print('The requested directory does not exist in history.')
 354                  return
 355              else:
 356                  opts = {}
 357          elif parameter_s.startswith('--'):
 358              ps = None
 359              fallback = None
 360              pat = parameter_s[2:]
 361              dh = self.shell.user_ns['_dh']
 362              # first search only by basename (last component)
 363              for ent in reversed(dh):
 364                  if pat in os.path.basename(ent) and os.path.isdir(ent):
 365                      ps = ent
 366                      break
 367  
 368                  if fallback is None and pat in ent and os.path.isdir(ent):
 369                      fallback = ent
 370  
 371              # if we have no last part match, pick the first full path match
 372              if ps is None:
 373                  ps = fallback
 374  
 375              if ps is None:
 376                  print("No matching entry in directory history")
 377                  return
 378              else:
 379                  opts = {}
 380  
 381  
 382          else:
 383              opts, ps = self.parse_options(parameter_s, 'qb', mode='string')
 384          # jump to previous
 385          if ps == '-':
 386              try:
 387                  ps = self.shell.user_ns['_dh'][-2]
 388              except IndexError as e:
 389                  raise UsageError('%cd -: No previous directory to change to.') from e
 390          # jump to bookmark if needed
 391          else:
 392              if not os.path.isdir(ps) or 'b' in opts:
 393                  bkms = self.shell.db.get('bookmarks', {})
 394  
 395                  if ps in bkms:
 396                      target = bkms[ps]
 397                      print('(bookmark:%s) -> %s' % (ps, target))
 398                      ps = target
 399                  else:
 400                      if 'b' in opts:
 401                          raise UsageError("Bookmark '%s' not found.  "
 402                                "Use '%%bookmark -l' to see your bookmarks." % ps)
 403  
 404          # at this point ps should point to the target dir
 405          if ps:
 406              try:
 407                  os.chdir(os.path.expanduser(ps))
 408                  if hasattr(self.shell, 'term_title') and self.shell.term_title:
 409                      set_term_title(self.shell.term_title_format.format(cwd=abbrev_cwd()))
 410              except OSError:
 411                  print(sys.exc_info()[1])
 412              else:
 413                  cwd = pathlib.Path.cwd()
 414                  dhist = self.shell.user_ns['_dh']
 415                  if oldcwd != cwd:
 416                      dhist.append(cwd)
 417                      self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
 418  
 419          else:
 420              os.chdir(self.shell.home_dir)
 421              if hasattr(self.shell, 'term_title') and self.shell.term_title:
 422                  set_term_title(self.shell.term_title_format.format(cwd="~"))
 423              cwd = pathlib.Path.cwd()
 424              dhist = self.shell.user_ns['_dh']
 425  
 426              if oldcwd != cwd:
 427                  dhist.append(cwd)
 428                  self.shell.db['dhist'] = compress_dhist(dhist)[-100:]
 429          if not 'q' in opts and not self.cd_force_quiet and self.shell.user_ns['_dh']:
 430              print(self.shell.user_ns['_dh'][-1])
 431  
 432      @line_magic
 433      def env(self, parameter_s=''):
 434          """Get, set, or list environment variables.
 435  
 436          Usage:\\
 437  
 438            :``%env``: lists all environment variables/values
 439            :``%env var``: get value for var
 440            :``%env var val``: set value for var
 441            :``%env var=val``: set value for var
 442            :``%env var=$val``: set value for var, using python expansion if possible
 443          """
 444          if parameter_s.strip():
 445              split = '=' if '=' in parameter_s else ' '
 446              bits = parameter_s.split(split)
 447              if len(bits) == 1:
 448                  key = parameter_s.strip()
 449                  if key in os.environ:
 450                      return os.environ[key]
 451                  else:
 452                      err = "Environment does not have key: {0}".format(key)
 453                      raise UsageError(err)
 454              if len(bits) > 1:
 455                  return self.set_env(parameter_s)
 456          env = dict(os.environ)
 457          # hide likely secrets when printing the whole environment
 458          for key in list(env):
 459              if any(s in key.lower() for s in ('key', 'token', 'secret')):
 460                  env[key] = '<hidden>'
 461  
 462          return env
 463  
 464      @line_magic
 465      def set_env(self, parameter_s):
 466          """Set environment variables.  Assumptions are that either "val" is a
 467          name in the user namespace, or val is something that evaluates to a
 468          string.
 469  
 470          Usage:\\
 471            :``%set_env var val``: set value for var
 472            :``%set_env var=val``: set value for var
 473            :``%set_env var=$val``: set value for var, using python expansion if possible
 474          """
 475          split = '=' if '=' in parameter_s else ' '
 476          bits = parameter_s.split(split, 1)
 477          if not parameter_s.strip() or len(bits)<2:
 478              raise UsageError("usage is 'set_env var=val'")
 479          var = bits[0].strip()
 480          val = bits[1].strip()
 481          if re.match(r'.*\s.*', var):
 482              # an environment variable with whitespace is almost certainly
 483              # not what the user intended.  what's more likely is the wrong
 484              # split was chosen, ie for "set_env cmd_args A=B", we chose
 485              # '=' for the split and should have chosen ' '.  to get around
 486              # this, users should just assign directly to os.environ or use
 487              # standard magic {var} expansion.
 488              err = "refusing to set env var with whitespace: '{0}'"
 489              err = err.format(val)
 490              raise UsageError(err)
 491          os.environ[var] = val
 492          print('env: {0}={1}'.format(var,val))
 493  
 494      @line_magic
 495      def pushd(self, parameter_s=''):
 496          """Place the current dir on stack and change directory.
 497  
 498          Usage:\\
 499            %pushd ['dirname']
 500          """
 501  
 502          dir_s = self.shell.dir_stack
 503          tgt = os.path.expanduser(parameter_s)
 504          cwd = os.getcwd().replace(self.shell.home_dir,'~')
 505          if tgt:
 506              self.cd(parameter_s)
 507          dir_s.insert(0,cwd)
 508          return self.shell.run_line_magic('dirs', '')
 509  
 510      @line_magic
 511      def popd(self, parameter_s=''):
 512          """Change to directory popped off the top of the stack.
 513          """
 514          if not self.shell.dir_stack:
 515              raise UsageError("%popd on empty stack")
 516          top = self.shell.dir_stack.pop(0)
 517          self.cd(top)
 518          print("popd ->",top)
 519  
 520      @line_magic
 521      def dirs(self, parameter_s=''):
 522          """Return the current directory stack."""
 523  
 524          return self.shell.dir_stack
 525  
 526      @line_magic
 527      def dhist(self, parameter_s=''):
 528          """Print your history of visited directories.
 529  
 530          %dhist       -> print full history\\
 531          %dhist n     -> print last n entries only\\
 532          %dhist n1 n2 -> print entries between n1 and n2 (n2 not included)\\
 533  
 534          This history is automatically maintained by the %cd command, and
 535          always available as the global list variable _dh. You can use %cd -<n>
 536          to go to directory number <n>.
 537  
 538          Note that most of time, you should view directory history by entering
 539          cd -<TAB>.
 540  
 541          """
 542  
 543          dh = self.shell.user_ns['_dh']
 544          if parameter_s:
 545              try:
 546                  args = map(int,parameter_s.split())
 547              except:
 548                  self.arg_err(self.dhist)
 549                  return
 550              if len(args) == 1:
 551                  ini,fin = max(len(dh)-(args[0]),0),len(dh)
 552              elif len(args) == 2:
 553                  ini,fin = args
 554                  fin = min(fin, len(dh))
 555              else:
 556                  self.arg_err(self.dhist)
 557                  return
 558          else:
 559              ini,fin = 0,len(dh)
 560          print('Directory history (kept in _dh)')
 561          for i in range(ini, fin):
 562              print("%d: %s" % (i, dh[i]))
 563  
 564      @skip_doctest
 565      @line_magic
 566      def sc(self, parameter_s=''):
 567          """Shell capture - run shell command and capture output (DEPRECATED use !).
 568  
 569          DEPRECATED. Suboptimal, retained for backwards compatibility.
 570  
 571          You should use the form 'var = !command' instead. Example:
 572  
 573           "%sc -l myfiles = ls ~" should now be written as
 574  
 575           "myfiles = !ls ~"
 576  
 577          myfiles.s, myfiles.l and myfiles.n still apply as documented
 578          below.
 579  
 580          --
 581          %sc [options] varname=command
 582  
 583          IPython will run the given command using commands.getoutput(), and
 584          will then update the user's interactive namespace with a variable
 585          called varname, containing the value of the call.  Your command can
 586          contain shell wildcards, pipes, etc.
 587  
 588          The '=' sign in the syntax is mandatory, and the variable name you
 589          supply must follow Python's standard conventions for valid names.
 590  
 591          (A special format without variable name exists for internal use)
 592  
 593          Options:
 594  
 595            -l: list output.  Split the output on newlines into a list before
 596            assigning it to the given variable.  By default the output is stored
 597            as a single string.
 598  
 599            -v: verbose.  Print the contents of the variable.
 600  
 601          In most cases you should not need to split as a list, because the
 602          returned value is a special type of string which can automatically
 603          provide its contents either as a list (split on newlines) or as a
 604          space-separated string.  These are convenient, respectively, either
 605          for sequential processing or to be passed to a shell command.
 606  
 607          For example::
 608  
 609              # Capture into variable a
 610              In [1]: sc a=ls *py
 611  
 612              # a is a string with embedded newlines
 613              In [2]: a
 614              Out[2]: 'setup.py\\nwin32_manual_post_install.py'
 615  
 616              # which can be seen as a list:
 617              In [3]: a.l
 618              Out[3]: ['setup.py', 'win32_manual_post_install.py']
 619  
 620              # or as a whitespace-separated string:
 621              In [4]: a.s
 622              Out[4]: 'setup.py win32_manual_post_install.py'
 623  
 624              # a.s is useful to pass as a single command line:
 625              In [5]: !wc -l $a.s
 626                146 setup.py
 627                130 win32_manual_post_install.py
 628                276 total
 629  
 630              # while the list form is useful to loop over:
 631              In [6]: for f in a.l:
 632                 ...:      !wc -l $f
 633                 ...:
 634              146 setup.py
 635              130 win32_manual_post_install.py
 636  
 637          Similarly, the lists returned by the -l option are also special, in
 638          the sense that you can equally invoke the .s attribute on them to
 639          automatically get a whitespace-separated string from their contents::
 640  
 641              In [7]: sc -l b=ls *py
 642  
 643              In [8]: b
 644              Out[8]: ['setup.py', 'win32_manual_post_install.py']
 645  
 646              In [9]: b.s
 647              Out[9]: 'setup.py win32_manual_post_install.py'
 648  
 649          In summary, both the lists and strings used for output capture have
 650          the following special attributes::
 651  
 652              .l (or .list) : value as list.
 653              .n (or .nlstr): value as newline-separated string.
 654              .s (or .spstr): value as space-separated string.
 655          """
 656  
 657          opts,args = self.parse_options(parameter_s, 'lv')
 658          # Try to get a variable name and command to run
 659          try:
 660              # the variable name must be obtained from the parse_options
 661              # output, which uses shlex.split to strip options out.
 662              var,_ = args.split('=', 1)
 663              var = var.strip()
 664              # But the command has to be extracted from the original input
 665              # parameter_s, not on what parse_options returns, to avoid the
 666              # quote stripping which shlex.split performs on it.
 667              _,cmd = parameter_s.split('=', 1)
 668          except ValueError:
 669              var,cmd = '',''
 670          # If all looks ok, proceed
 671          split = 'l' in opts
 672          out = self.shell.getoutput(cmd, split=split)
 673          if 'v' in opts:
 674              print('%s ==\n%s' % (var, pformat(out)))
 675          if var:
 676              self.shell.user_ns.update({var:out})
 677          else:
 678              return out
 679  
 680      @line_cell_magic
 681      def sx(self, line='', cell=None):
 682          """Shell execute - run shell command and capture output (!! is short-hand).
 683  
 684          %sx command
 685  
 686          IPython will run the given command using commands.getoutput(), and
 687          return the result formatted as a list (split on '\\n').  Since the
 688          output is _returned_, it will be stored in ipython's regular output
 689          cache Out[N] and in the '_N' automatic variables.
 690  
 691          Notes:
 692  
 693          1) If an input line begins with '!!', then %sx is automatically
 694          invoked.  That is, while::
 695  
 696            !ls
 697  
 698          causes ipython to simply issue system('ls'), typing::
 699  
 700            !!ls
 701  
 702          is a shorthand equivalent to::
 703  
 704            %sx ls
 705  
 706          2) %sx differs from %sc in that %sx automatically splits into a list,
 707          like '%sc -l'.  The reason for this is to make it as easy as possible
 708          to process line-oriented shell output via further python commands.
 709          %sc is meant to provide much finer control, but requires more
 710          typing.
 711  
 712          3) Just like %sc -l, this is a list with special attributes:
 713          ::
 714  
 715            .l (or .list) : value as list.
 716            .n (or .nlstr): value as newline-separated string.
 717            .s (or .spstr): value as whitespace-separated string.
 718  
 719          This is very useful when trying to use such lists as arguments to
 720          system commands."""
 721          
 722          if cell is None:
 723              # line magic
 724              return self.shell.getoutput(line)
 725          else:
 726              opts,args = self.parse_options(line, '', 'out=')
 727              output = self.shell.getoutput(cell)
 728              out_name = opts.get('out', opts.get('o'))
 729              if out_name:
 730                  self.shell.user_ns[out_name] = output
 731              else:
 732                  return output
 733  
 734      system = line_cell_magic('system')(sx)
 735      bang = cell_magic('!')(sx)
 736  
 737      @line_magic
 738      def bookmark(self, parameter_s=''):
 739          """Manage IPython's bookmark system.
 740  
 741          %bookmark <name>       - set bookmark to current dir
 742          %bookmark <name> <dir> - set bookmark to <dir>
 743          %bookmark -l           - list all bookmarks
 744          %bookmark -d <name>    - remove bookmark
 745          %bookmark -r           - remove all bookmarks
 746  
 747          You can later on access a bookmarked folder with::
 748  
 749            %cd -b <name>
 750  
 751          or simply '%cd <name>' if there is no directory called <name> AND
 752          there is such a bookmark defined.
 753  
 754          Your bookmarks persist through IPython sessions, but they are
 755          associated with each profile."""
 756  
 757          opts,args = self.parse_options(parameter_s,'drl',mode='list')
 758          if len(args) > 2:
 759              raise UsageError("%bookmark: too many arguments")
 760  
 761          bkms = self.shell.db.get('bookmarks',{})
 762  
 763          if 'd' in opts:
 764              try:
 765                  todel = args[0]
 766              except IndexError as e:
 767                  raise UsageError(
 768                      "%bookmark -d: must provide a bookmark to delete") from e
 769              else:
 770                  try:
 771                      del bkms[todel]
 772                  except KeyError as e:
 773                      raise UsageError(
 774                          "%%bookmark -d: Can't delete bookmark '%s'" % todel) from e
 775  
 776          elif 'r' in opts:
 777              bkms = {}
 778          elif 'l' in opts:
 779              bks = sorted(bkms)
 780              if bks:
 781                  size = max(map(len, bks))
 782              else:
 783                  size = 0
 784              fmt = '%-'+str(size)+'s -> %s'
 785              print('Current bookmarks:')
 786              for bk in bks:
 787                  print(fmt % (bk, bkms[bk]))
 788          else:
 789              if not args:
 790                  raise UsageError("%bookmark: You must specify the bookmark name")
 791              elif len(args)==1:
 792                  bkms[args[0]] = os.getcwd()
 793              elif len(args)==2:
 794                  bkms[args[0]] = args[1]
 795          self.shell.db['bookmarks'] = bkms
 796  
 797      @line_magic
 798      def pycat(self, parameter_s=''):
 799          """Show a syntax-highlighted file through a pager.
 800  
 801          This magic is similar to the cat utility, but it will assume the file
 802          to be Python source and will show it with syntax highlighting.
 803  
 804          This magic command can either take a local filename, an url,
 805          an history range (see %history) or a macro as argument.
 806  
 807          If no parameter is given, prints out history of current session up to
 808          this point. ::
 809  
 810          %pycat myscript.py
 811          %pycat 7-27
 812          %pycat myMacro
 813          %pycat http://www.example.com/myscript.py
 814          """
 815          try:
 816              cont = self.shell.find_user_code(parameter_s, skip_encoding_cookie=False)
 817          except (ValueError, IOError):
 818              print("Error: no such file, variable, URL, history range or macro")
 819              return
 820  
 821          page.page(self.shell.pycolorize(source_to_unicode(cont)))
 822  
 823      @magic_arguments.magic_arguments()
 824      @magic_arguments.argument(
 825          '-a', '--append', action='store_true', default=False,
 826          help='Append contents of the cell to an existing file. '
 827               'The file will be created if it does not exist.'
 828      )
 829      @magic_arguments.argument(
 830          'filename', type=str,
 831          help='file to write'
 832      )
 833      @cell_magic
 834      def writefile(self, line, cell):
 835          """Write the contents of the cell to a file.
 836  
 837          The file will be overwritten unless the -a (--append) flag is specified.
 838          """
 839          args = magic_arguments.parse_argstring(self.writefile, line)
 840          if re.match(r'^(\'.*\')|(".*")$', args.filename):
 841              filename = os.path.expanduser(args.filename[1:-1])
 842          else:
 843              filename = os.path.expanduser(args.filename)
 844              
 845          if os.path.exists(filename):
 846              if args.append:
 847                  print("Appending to %s" % filename)
 848              else:
 849                  print("Overwriting %s" % filename)
 850          else:
 851              print("Writing %s" % filename)
 852          
 853          mode = 'a' if args.append else 'w'
 854          with io.open(filename, mode, encoding='utf-8') as f:
 855              f.write(cell)
 856