Last active
May 13, 2016 07:28
-
-
Save toofar/24d9ca7398201777da0f2e3b67c7d925 to your computer and use it in GitHub Desktop.
Bash/Readline function to insert previously used files into the current command line.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
maybefile () { | |
case "$1" in | |
*/*|/*|*/) | |
return 0;; | |
esac | |
return 1 | |
} | |
# Parse shell commands (eg from `history`) and look for parameters that look | |
# like files, that is they contain a '/'. Handles quoting and backslash | |
# escaping but not variable substition etc. | |
# If you want it to strip quote and \ chars just put c="" wherever the | |
# variable quote is set and unset and wherever escape is set. | |
path_filter() { | |
nth="$1" | |
matches=0 | |
mapfile BUF | |
for ((j="${#BUF[@]}"; j>=0; j--)) ;do | |
l="${BUF[$j]}" | |
llen="${#l}" | |
i=0 | |
escape='' | |
quote="" | |
tok="" | |
while [ $i -le $llen ] ;do | |
c=${l:$i:1} | |
case "$c" in | |
' ') | |
if [ -z "$quote" ] && [ -z "$escape" ];then | |
if maybefile "$tok" ;then | |
matches=$(($matches+1)) | |
if [ -z "$nth" ] || [ $matches -eq $nth ] ;then | |
printf "%s\n" "$tok" | |
[ -n "$nth" ] && return | |
fi | |
fi | |
tok="" | |
c="" | |
fi | |
escape="" | |
;; | |
'"'|"'") | |
if [ -z "$quote" ] && [ -z "$escape" ];then | |
quote="$c" | |
elif [ -n "$quote" ] && [ -z "$escape" ];then | |
[ "$quote" = "$c" ] && quote="" | |
fi | |
escape="" | |
;; | |
"\\") | |
if [ -z "$quote" ] && [ -z "$escape" ];then | |
escape="\\" | |
elif [ -n "$quote" ] && [ -z "$escape" ];then | |
if [ "$quote" = '"' ];then | |
escape="\\" | |
else | |
escape="" | |
fi | |
else | |
escape="" | |
fi | |
;; | |
*) | |
escape="" | |
;; | |
esac | |
[ -n "$c" ] && tok="$tok$c" | |
i=$(($i+1)) | |
done | |
if [ -n "$tok" ];then | |
if maybefile "$tok" ;then | |
matches=$(($matches+1)) | |
if [ -z "$nth" ] || [ $matches -eq $nth ] ;then | |
printf "%s\n" "$tok" | |
[ -n "$nth" ] && return | |
fi | |
fi | |
tok="" | |
fi | |
done | |
} | |
# function to cycle between last path like command line arguments used. | |
last_file_cycle() { | |
# Clear our saved variables if HISTCMD changes or the command line has | |
# changed | |
if [ -n "$LFC_HISTCMD" ] && [ "$HISTCMD" != "$LFC_HISTCMD" ];then | |
unset LFC_HISTCMD | |
unset LFC_ORIG_RLLINE | |
unset LFC_ORIG_RLPOINT | |
elif [ "$LFC_NEW_RLLINE" != "$READLINE_LINE" ];then | |
unset LFC_HISTCMD | |
unset LFC_ORIG_RLLINE | |
unset LFC_ORIG_RLPOINT | |
fi | |
if [ -z "$LFC_HISTCMD" ];then | |
LFC_ORIG_RLLINE="${READLINE_LINE}" | |
LFC_ORIG_RLPOINT="${READLINE_POINT}" | |
LFC_HISTCMD="$HISTCMD" | |
LFC_CYCLE=0 | |
fi | |
LF=`history 100 | path_filter $(($LFC_CYCLE+1))` | |
LFC_CYCLE=$(($LFC_CYCLE+1)) | |
LFC_NEW_RLLINE="${LFC_ORIG_RLLINE:0:LFC_ORIG_RLPOINT}$LF${LFC_ORIG_RLLINE:LFC_ORIG_RLPOINT}" | |
READLINE_LINE="${LFC_NEW_RLLINE}" | |
READLINE_POINT=$((LFC_ORIG_RLPOINT + ${#LF})) | |
} | |
bind -x '"\C-f":last_file_cycle' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
from __future__ import print_function, with_statement | |
import shlex | |
import fileinput | |
import argparse | |
import sys | |
import subprocess | |
import os.path | |
from math import log | |
helpmsg=""" | |
Select path like strings out of input. Eg anything with a path seperator ('/') | |
in it. Understands shell quoting. See also facebook path picker. | |
""" | |
parser = argparse.ArgumentParser(description=helpmsg) | |
parser.add_argument('files', help='read input from FILES', | |
metavar='FILES', action='append', nargs='*') | |
parser.add_argument('-r', '--reverse', help='print last matched path(s) first', | |
action='store_true', default=False) | |
parser.add_argument('-n', '--num', | |
help='prepend each printed line with a number'+ | |
"left padded with zeroes", | |
action='store_true', default=False) | |
parser.add_argument('-l', '--limit', help='stop parsing after N paths', | |
type=int, metavar='N') | |
parser.add_argument('-q', '--quiet', help='don\'t print out paths '+ | |
'(except for the purposes of --select)', | |
action='store_true', default=False) | |
parser.add_argument('-c', '--copy', help='copy results to primary selection '+ | |
'(requires xclip)', | |
action='store_true', default=False) | |
parser.add_argument('--clipboard', help='copy results to clipboard '+ | |
'(requires xclip)', | |
action='store_true', default=False) | |
# _NOPE_ = not passed, None = passed without arg | |
parser.add_argument('-s', '--select', help='prompt for selection(s)'+ | |
'from numbered list (eg 1,3-7,10 or substring)', | |
metavar='selection', nargs='?', default='_NOPE_') | |
#TODO: could use fnmatch and listdir to support globbing ... but would | |
#have to walk dirs? Surely there is some lib which handles it | |
parser.add_argument('-e', '--exists', help='check a file exists before '+ | |
'including it (no glob support)', | |
action='store_true', default=False) | |
#parser.add_argument('-f', '--fuzzy', help='start fuzzy matcher to select from ' | |
# +' results (requires dmenu)', | |
# action='store_true', default=False) | |
args = parser.parse_args() | |
lpad_str = "{n:0{width}d} {}" | |
limit = 0 | |
def display(paths, for_select=False): | |
if limit and not for_select: | |
paths = paths[0:limit] | |
num_paths=len(paths) | |
fmt_str = "{n:0{width}d} {}" if args.num or for_select else "{}" | |
for i, p in enumerate(paths): | |
if not args.quiet or for_select: | |
print(fmt_str.format(p, n=i, width=int(log(num_paths, 10)+1))) | |
if args.copy: | |
p = subprocess.Popen(["xclip"], stdin=subprocess.PIPE) | |
p.stdin.write("\n".join(paths)+'\n') | |
if args.clipboard: | |
subprocess.Popen(["xclip", "--selection=clipboard"], | |
stdin=subprocess.PIPE) | |
p.stdin.write("\n".join(paths)+'\n') | |
def str_to_ind(s): | |
try: | |
if "-" in s: | |
return slice(*map(int,s.split('-'))) | |
else: | |
return int(s) | |
except ValueError: | |
pass | |
# must be a substring | |
return s | |
def select(paths): | |
if args.select is '_NOPE_' or not args.select: | |
sys.stdout.write("Select an option (eg 1,3-7,10 or substring): ") | |
sys.stdout.flush() | |
old_stdin = sys.stdin | |
if not sys.stdin.isatty(): | |
sys.stdin = None | |
try: | |
sys.stdin = open('/dev/tty') | |
except IOError: | |
pass | |
if not sys.stdin and sys.stdout.isatty(): | |
sys.stdin = sys.stdout | |
elif not sys.stdin and sys.stderr.isatty(): | |
sys.stdin = sys.sterr | |
ranges = sys.stdin.readline().strip() | |
sys.stdin = old_stdin | |
print("") | |
else: | |
ranges = args.select | |
ranges=map(str_to_ind, ranges.split(',')) | |
ret=[] | |
for r in ranges: | |
try: | |
if isinstance(r, int): | |
ret += [paths[r]] | |
elif isinstance(r, str): | |
ret += [p for p in paths if r in p] | |
else: | |
ret += paths[r] | |
except IndexError as e: | |
pass | |
return ret | |
files = [] | |
# Seperate positional args into -[0-9]+ and files | |
# Sadly can't use these number short arguments clumped up with other ones :( | |
for a in args.files[0]: | |
if a[0] == '-': | |
try: | |
l = int(a[1:]) | |
limit = l | |
continue | |
except: | |
pass | |
files.append(a) | |
old_stdout = sys.stdout | |
if args.select is not "_NOPE_": | |
# We need to display a list of options and prompt the user for their | |
# selection. Assume we are interactive and hijack tty. This is for | |
# the case of eg vim `history 10 | ~/bin/sfs -1 -s` | |
if not sys.stdout.isatty(): | |
sys.stdout = None | |
try: | |
sys.stdout = open('/dev/tty', 'w') | |
except IOError: | |
pass | |
if not sys.stdout and sys.stdin.isatty(): | |
sys.stdout = sys.stdin | |
elif not sys.stdout and sys.stderr.isatty(): | |
sys.stdout = sys.sterr | |
paths = [] | |
for line in fileinput.input(files=files): | |
cmd = shlex.split(line) | |
if args.exists: | |
paths += map(os.path.expanduser, filter(lambda x: '/' in x and | |
os.path.exists(os.path.expanduser(x)), cmd)) | |
else: | |
paths += filter(lambda x: '/' in x, cmd) | |
if not args.reverse and limit: | |
if args.select is '_NOPE_' and args.limit <= len(paths): | |
if args.reverse: paths=list(reversed(paths)) | |
if not args.select or args.select is "_NOPE_": | |
display(paths, args.select is not "_NOPE_") | |
if args.select is not "_NOPE_": | |
paths=select(paths) | |
sys.stdout = old_stdout | |
display(paths) | |
sys.exit(0) | |
if args.reverse: paths=list(reversed(paths)) | |
if not args.select or args.select is "_NOPE_": | |
display(paths, args.select is not "_NOPE_") | |
if args.select is not "_NOPE_": | |
paths=select(paths) | |
sys.stdout = old_stdout | |
display(paths) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment