Обновление завершения вкладки Bash при смене каталога

Благодаря помощи meuh я смог написать короткую программу на Python, которая преобразует PDF-файл, созданный с помощью моей существующей программы, в файл PostScript с помощью команд, setpagedeviceспецифичных для устройства -.

Эта программа весьма специфична для моего использования, но я думаю, что она может быть полезна в качестве отправной точки для других. Вот он:

#! /usr/bin/env python3

import argparse
import re
import subprocess
import sys

import PyPDF2

def make_postscript(f):
    """Convert a PDF file to PostScript, with pdf2ps, and yield it line by line."""
    with subprocess.Popen(['pdf2ps', '/dev/stdin', '/dev/stdout'], stdin=f, stdout=subprocess.PIPE, stderr=None) as proc:
        for line in proc.stdout:
            yield line

def add_device_control(postscript, separator_pages):
    """Add device control commands to a PostScript file with DSC comments."""
    DSC_page_re = re.compile(b'%%Page: (?P<page_name>.+) (?P<page_number>[1-9][0-9]*)$')
    DSC_begin_page_setup_re = re.compile(b'%%BeginPageSetup$')
    page_number = None
    for line in postscript:
        m = DSC_page_re.match(line)
        if m:
            assert page_number is None
            page_number = int(m.group('page_number').decode('ASCII'))-1
            yield line
            continue
        m = DSC_begin_page_setup_re.match(line)
        if m:
            assert page_number is not None
            yield line
            if page_number in separator_pages:
                yield b'mark { << /PageSize [1191 842] /ImagingBBox null /MediaType (Plain) /MediaPosition null >> setpagedevice } stopped cleartomark\n'
            elif page_number not in separator_pages:
                yield b'mark { << /PageSize [595 842] /ImagingBBox null /MediaType (Labels) /MediaPosition 0 >> setpagedevice } stopped cleartomark\n'
            page_number = None
            continue
        yield line
    assert page_number is None

def walk_outline(outline, depth=0):
    """Walk through the outline of a PDF file in a depth-first search way, and yield each element with its zero-based depth."""
    for obj in outline:
        if isinstance(obj, PyPDF2.pdf.Destination):
            yield depth, obj
        elif isinstance(obj, list):
            for result in walk_outline(obj, depth+1):
                yield result
        else:
            assert False

def find_separator_pages(f):
    """Find the page number of the separator pages in a PDF file"""
    separator_pages = set()
    reader = PyPDF2.PdfFileReader(f)
    for depth, obj in walk_outline(reader.outlines):
        page_num = reader._getPageNumberByIndirect(obj.page)
        if depth == 0:
            assert page_num >= 0
            separator_pages.add(page_num)
    return separator_pages

def main():
    parser = argparse.ArgumentParser()
    parser.add_argument("input_file", metavar="input.pdf", type=argparse.FileType('rb'))
    parser.add_argument("output_file", metavar="output.ps", nargs="?", type=argparse.FileType('wb'), default=sys.stdout.buffer)
    args = parser.parse_args()

    separator_pages = find_separator_pages(args.input_file)
    args.input_file.seek(0)
    postscript = make_postscript(args.input_file)
    postscript = add_device_control(postscript, separator_pages)
    for line in postscript:
        args.output_file.write(line)

if __name__ == "__main__":
    main()

Еще раз огромное спасибо meuh за помощь в комментариях.

2
18.11.2021, 12:18
3 ответа

Вот альтернативная реализация вашей upфункции (, включая ее аналог программируемого завершения ), который пытается:

  • работа с произвольными путями (включая пробелы и новые строки );
  • избегайте изменения IFSпеременной, что не является тривиальным для правильного восстановления ;
  • явно позаботиться о некоторых специальных каталогах (., ..; обрабатывается здесь произвольным образом, вы можете адаптировать код в соответствии со своими предпочтениями );

Функция up:

up () {
  local path=$PWD
  case $1 in
    (''|'..')
      cd..; return ;;
    (.)
      cd.; return ;;
    (/)
      cd /; return ;;
    (*/*)
      printf '"%s" %s\n' "$1" 'contains forward slashes, up will not work' >&2
      return 1 ;;
  esac
  if [[ $path != /* ]]; then
    printf '%s\n' 'PWD is not an absolute path, up will not work' >&2
    return 1
  fi
  until [[ $path = / ]]; do
    path=${path%/*}
    path=${path:-/}
    if [[ ${path%"$1"} != "$path" ]] # You may add && [[ -d ${path%"$1"} ]] to
                            # make "up oo" fail when you are in `/foo/bar`,
                            # while "up foo" would succeed anyway
    then
      cd -- "$path"
      return
    fi
  done
  printf '%s "%s" %s\n' 'Directory' "$1" 'not found' >&2
  return 1
}

И ее _upфункция завершения и спецификация (команды complete):

_up () {
  local cur last path
  declare -A ancestors
  cur=${COMP_WORDS[$COMP_CWORD]}
  path=$PWD
  if [[ $path != /* ]]; then
    return 1
  fi
  until [[ -z $path ]]; do
    path=${path%/*}
    last=${path##*/}
    last=${last:-/}
    if [[ $last == "${cur}"* ]]; then
      ancestors+=( ["$last"]= )
    fi
  done
  COMPREPLY=( "${!ancestors[@]}" )
  return
}
complete -o nospace -o filenames -F _up up

Оба этих фрагмента кода предназначены для размещения в вашем .bashrc. Если вы используете bash -завершение , вы можете предпочесть хранить функции завершения и спецификации в $XDG_DATA_HOME/bash-completion, ~/.local/share/bash-completionили ~/.bash_completion(. Обратитесь к FAQ на подписанной странице, чтобы узнать больше об этом )..

Аргументы, содержащие косую черту, исключаются намеренно, потому что:в противном случае (на мой взгляд ), помимо foo/barмы должны позаботиться, например, оfoo///bar/./../baz(т.е. выполнить некоторую форму канонизации ).

Для использования ассоциативных массивов(declare -A)требуется Bash >= 4.0.

4
18.11.2021, 20:16

Проблема в том, что оценка переменных LSи LSARRAYпроисходит только один раз, поэтому команда upвсегда будет выполняться с родительскими папками PWD, которые у вас были, когда вы определили завершение.

Для динамического извлечения родительских папок вам необходимо создать функцию завершения. Таким образом, переменные LSи LSARRAYбудут оцениваться каждый раз, когда вы пытаетесь завершить свою базу команд up на вашем фактическом текущем PWD.

_complete_up()
{
  local cur=${COMP_WORDS[COMP_CWORD]}
  local LS=$(echo $PWD)
  local LSARRAY=${LS//\// }
  COMPREPLY=( $(compgen -W "${LSARRAY}" -- $cur) )
  return 0
}

complete -F _complete_up up
3
18.11.2021, 13:41

Вы можете сделать это:

_up () {
        local oldIFS="$IFS" LS="${PWD%/*}"
        IFS='/
'
        if [ -n "$2" ]; then
                COMPREPLY=($(compgen -W "${LS#/}" "$2"))
        else
                COMPREPLY=(${LS#/})
        fi
        IFS="$oldIFS"
}

complete -F _up up

up () {
        local oldIFS="$IFS" i=1 LSARRAY LS="$PWD" FIND ARR
        IFS='/
'
        LSARRAY=(${PWD#/})
        if [[ $LS == */$1/* ]]; then
                FIND=$1
                while [ "${LSARRAY[${#LSARRAY[@]}-$i]}" != "$FIND" ] && [ $i != ${#LSARRAY[@]} ]; do
                        cd..
                        i=$(($i+1))
                done
        else
                echo "Did not find directory '$1'"
        fi
        IFS="$oldIFS"
}

Основная проблема заключалась (, как упоминалось в предыдущем ответе ), в том, что ваши возможности завершения не генерировались динамически.

Кроме того, ваш код не работает с пробелами в имени каталога. Мой не работает с новыми строками в имени каталога.

Я также удалил текущий каталог из списка выбора, потому что это не имеет смысла.

Я не понимаю, что ваш if [[ $1 == */* ]]должен делать (в любом случае должен быть if [[ $FIND == */* ]]), так как имена каталогов никогда не могут содержать /, поэтому я удалил его.

2
18.11.2021, 14:45

Теги

Похожие вопросы