본문 바로가기
놀기/Linux

디렉토리 이동 프로그램

by Hi~ 2021. 7. 26.

제목이 좀 이상한데...

"A shell utility allowing users to navigate to aliased directories"와 비스무리 한 것이다.

 

10여 년 전 리눅스에서 개발할 때 디렉터리 이동이 귀찮아 ncurses 라이브러리 이용해서 C언어로 UI 만들고 shell 스크립트로 실행해서 사용했었는데, 소스코드를 못 찾겠어서 인터넷 뒤지다 보니 아래와 같은 프로그램을 찾았다. 

 

https://github.com/iridakos/goto

 

GitHub - iridakos/goto: Alias and navigate to directories with tab completion in Linux

Alias and navigate to directories with tab completion in Linux - GitHub - iridakos/goto: Alias and navigate to directories with tab completion in Linux

github.com

자동 완성 기능도 지원되고 아주 좋은 프로그램이다. 그런데 문제는 이것조차도 귀찮다는 것이다. 그냥 키보드 상하 키로 선택해서 엔터 치고 싶었다. 예전에 만든 것을 찾는 것은 포기했으니 만들어야지 어쩌겠는가 ㅠㅠ

 

예전에는 ncurses로 만들었는데 이제 머리가 썩어서 바닥부터 하기가 힘들다. 그래서 인터넷 뒤져서 python으로 만든 ncurses 예제를 구했다. 출처를 써야 하는데 까먹어서.... 아무튼 이 python 코드로 ncurses 부분 해결하고 나머지는 옛날 기억 더듬어서 비슷하게 만들었다. (예전 것이 더 예뻤는데...)

 

크게

1) 디렉터리 목록 파일 읽기

2) 화면 그리는 부분

3) 실행을 위한 shell script 및 .bashrc 수정

 

 


 

1) 디렉터리 목록 파일 읽기

    file_path = sys.argv[1]
    list_file = sys.argv[2]

    with open(list_file) as f:
        global golist_strings
        golist_strings = [tuple(map(lambda str:str.replace('\"', '').strip(), i.split(','))) for i in f]
    golist_cnt = len(golist_strings)

    max = 3
    for go in golist_strings :
        len_go = len(go[0]) + len(go[1]) + 3;
        if (len_go > max) :
            max = len_go

    max_line_length = max

    offset_y = 2 + display_per_page;

file_path는 옮겨갈 path를 shell script로 넘기는 용도로 만들었는데, 예전에는 이렇게 하지 않았는데 기억이 나지 않아 대충 했다. 그리고 list_file은 디렉터리 목록이 있는 파일이다. 

list_file 읽고 처리하는 것은 이전 게시물을 확인해 주시고...

나머지 부분은 화면 상의 위치 잡기 위해 문자열 길이 등을 계산하는 코드로 크게 중요하지 않다.

 

2021.07.26 - [일하기/Python] - map function 안에서 문자열 strip 및 replace 하기

 

map function 안에서 문자열 strip 및 replace 하기

file을 읽어 tuple 형식으로 만들려고 하는데 문자열에 따옴표(")도 있고 공란도 있고 개행 문자도 있고 할 때 한 번에 바꾸는 방법은??? Source Code #!/usr/bin/env python2 # -*- coding: UTF-8 -*- #kate: syn..

busyman.tistory.com

 

 


 

2) 화면 그리는 부분

소스 코드는 마지막에 있는 전체 코드를 참조하시면 되고...

대략 이런 형태로 간단하게 구성했다. 가운데에 목록 그리고 하단에 'q'를 누르면 나간다는 문구 정도다. 그리고 예외 처리를 하지 않아 터미널 크기를 아주 작게 만들면 예외가 발생하여 죽는다. 그냥 막 쓸 용도라 ㅎㅎㅎ

 

 


 

 

3) 실행을 위한 shell script 및 .bashrc 수정

#!/bin/bash

TMP_FILE=/tmp/`openssl rand -hex 32`
#echo $TMP_FILE

python $HOME/cmd/go.py $TMP_FILE $HOME/cmd/go.list

if [ -e $TMP_FILE ]
then
    NEW_DIR=`cat $TMP_FILE`
    #echo $NEW_DIR
    builtin cd $NEW_DIR 2>/dev/null && pwd
    rm $TMP_FILE
fi

 

별 것은 없고 python 파일 실행하고 결과를 받아서 이동하는 코드다.

 

실행은 .bashrc에 아래와 같이 추가하여 사용한다. 다들 알겠지만 .bashrc 파일은 home 디렉터리에 있고 이름은 원하는 것으로 변경하면 된다.

 

alias g='. $HOME/cmd/go.sh'

 

go.list 파일에는 디렉터리 정보가 들어 있는데 아래와 같이 구성한다.

 

"1", "/tmp/1"
"2", "/tmp/2"
"3", "/tmp/3"
"4", "/tmp/4"
"5", "/tmp/5"
"6", "/tmp/6"
"7", "/tmp/7"
"8", "/tmp/8"
"9", "/tmp/9"
"a", "/tmp/a"
"b", "/tmp/b"
"c", "/tmp/c"
"d", "/tmp/d"
"v", "/tmp/v"

 

참고로... go.sh, go.py, go.list 는 같은 디렉터리에 있어야 함.

 

 

이렇게 하고 실행하면 된다.

 

 


 

go.py 전체 소스

#!/usr/bin/env python2
# -*- coding: UTF-8 -*-
#kate: syntax Python ;

import os
import sys
import subprocess

'''function 1 on F1 1 and NUM-1'''

global file_path
global list_file

def moveTo(where):
    os.system("echo \"" + golist_strings[where-1][1] + "\" > " + file_path)
    sys.exit()
    return "move to " + golist_strings[where-1][1] + " " + str(where)

import sys,os
import curses

global menuE
global e
global keychar
global golist_strings

e       = 0
menuE   = 1   # active entry
keychar = " " # like 1 or 2 ...
max_line_length = 0
go_list_off = 0
display_per_page = 10

class switch(object):
    value = None
    def __new__(class_, value):
        class_.value = value
        return True

def case(*args):
    return any((arg == switch.value for arg in args))


def draw_menu(stdscr):
    global menuE
    global e
    global keychar
    global max_line_length
    global go_list_off

    k        = 0
    cursor_x = 0
    cursor_y = 0

    # Clear and refresh the screen for a blank canvas
    stdscr.clear()
    stdscr.refresh()

    # Start colors in curses
    curses.start_color()
    curses.init_pair(1, curses.COLOR_CYAN,  curses.COLOR_BLACK)
    curses.init_pair(2, curses.COLOR_RED,   curses.COLOR_BLACK)
    curses.init_pair(3, curses.COLOR_BLACK, curses.COLOR_WHITE)

    # Loop where k is the last character pressed
    while (k != ord('q')):

        # Initialization
        stdscr.clear()
        height, width = stdscr.getmaxyx()

        ki              = int(k)
        try:    keychar = chr(k)
        except: keychar = 0
        callfunc        = False

        while switch(k):
            if case(10, 32 , 83):   # SPACE  ,   ENTER  ,   83 mouse middle
                callfunc = True
                break
            pass
            break

        if callfunc:
            e = moveTo(menuE)

        if k == curses.KEY_DOWN:
            if menuE < golist_cnt :
                cursor_y = cursor_y + 1
                menuE = menuE + 1

        elif k == curses.KEY_UP:
            if menuE > 1  :
                cursor_y = cursor_y - 1
                menuE = menuE - 1

        cursor_x = max(0, cursor_x)
        cursor_x = min(width-1, cursor_x)

        cursor_y = max(0, cursor_y)
        cursor_y = min(height-1, cursor_y)

        # Declaration of strings
        title        = "Choose where you want to go."[:width-1]
        statusbarstr = "Press 'q' to exit"

        # Centering calculations
        start_x_title    = int((  width // 2) - (len(title)    // 2) - len(title)    % 2)
        start_x_subtitle = int((  width // 2) - (max_line_length // 2) - max_line_length % 2)
        start_y          = int(( height // 2) -  (offset_y // 2) - (offset_y % 2)) - 1

        # Rendering some text
        #whstr = "Width: {}, Height: {} {}".format(width, height, menuE)
        #stdscr.addstr(0, 0, whstr, curses.color_pair(1))

        # Render status bar
        stdscr.attron(curses.color_pair(3))
        stdscr.addstr(height-1,  0, statusbarstr)
        stdscr.addstr(height-1, len(statusbarstr), " " * (width - len(statusbarstr) - 1))
        stdscr.attroff(curses.color_pair(3))

        # Turning on attributes for title
        stdscr.attron(curses.color_pair(2))
        stdscr.attron(curses.A_BOLD)

        # Rendering title
        stdscr.addstr(start_y, start_x_title, title)

        # Turning off attributes for title
        stdscr.attroff(curses.color_pair(2))
        stdscr.attroff(curses.A_BOLD)

        if display_per_page < menuE :
            go_list_off = menuE - display_per_page 

        name_idx = 0
        display_offset = 0;
        for (name, path) in golist_strings:
            name_idx = name_idx + 1

            if go_list_off > name_idx :
                continue

            if display_offset > display_per_page :
                break

            display_offset = display_offset + 1

            if name_idx == menuE :
                stdscr.attron(curses.color_pair(3))
                stdscr.attron( curses.A_BOLD)
            else  :  
                stdscr.attroff(curses.color_pair(3))
                stdscr.attroff(curses.A_BOLD)

            stdscr.addstr(start_y + 1 + display_offset,  start_x_subtitle, name + " - " + path)

        # Refresh the screen
        stdscr.refresh()

        # Wait for next input
        k = stdscr.getch()


def main():
    global file_path
    global golist_strings
    global golist_cnt
    global max_line_length
    global offset_y
    global list_file

    file_path = sys.argv[1]
    list_file = sys.argv[2]

    with open(list_file) as f:
        global golist_strings
        golist_strings = [tuple(map(lambda str:str.replace('\"', '').strip(), i.split(','))) for i in f]
    golist_cnt = len(golist_strings)

    max = 3
    for go in golist_strings :
        len_go = len(go[0]) + len(go[1]) + 3;
        if (len_go > max) :
            max = len_go

    max_line_length = max

    offset_y = 2 + display_per_page;

    curses.wrapper(draw_menu)

if __name__ == "__main__":
    main()

go.list
0.00MB
go.py
0.00MB
go.sh
0.00MB

댓글