Command Aliases

프롬프트 상에서 자주 사용하는 명령문이나 복잡하고 긴 명령문에 별칭을 붙여서 사용할 수 있습니다. 별칭이 실행될 때는 설정한 명령문이 그대로 치환되어 실행됩니다. 설정할 경우 어떤 명령이나 함수 보다도 우선순위가 높습니다. 심지어 shell 키워드도 alias 하여 사용할 수 있습니다. alias 는 명령문을 alias 하는것으로 중간에 사용되는 인수를 alias 해서 사용할수 없고 명령 위치에서만 확장이 됩니다.

alias cp='cp -i'
alias mv='mv -i'
alias grep='grep --color=auto'
alias p4='ps axfo user,ppid,pid,pgid,sid,tty,stat,args'

alias geturl='python /some/cool/script.py'
$ geturl http://example.com/excitingstuff.jpg

# '|' ';' '{ }' '( )' 를 이용하여 여러 명령을 설정할 수도 있습니다.
alias name1='command1 | command2 | command3'
alias name2='command1; command2; command3'
alias name3='{ command1; command2; command3 ;}'
  • shell 프롬프트 상에서만 사용할 수 있습니다.
    non-interactive shell 인 스크립트 파일을 실행할 때는 기본적으로 alias 가 disable 됩니다.
    ( shopt -s expand_aliases 옵션으로 컨트롤할 수 있습니다. )

  • 설정한 alias 는 subshell 에서도 동일하게 적용됩니다.

  • child process 에서는 사용할 수 없습니다.
    ( 가령 vi 에디터에서 외부 명령을 실행할 때 사용할 수 없습니다. )

  • 함수를 정의할 때는 alias 가 자동으로 확장되어 정의됩니다.
    기존 alias 가 적용되지 않게 하려면 escape 하면됩니다.

$ alias ls grep
alias ls='ls -N -F --color=auto --group-directories-first'
alias grep='grep --color=auto'

$ lsd() { ls -Al "$@" | grep ^d ;}    # 디렉토리만 출력하는 함수를 정의

$ declare -f lsd  
lsd () 
{            # 기존에 ls, grep 명령에 설정되어 있던 alias 가 확장되어 정의된다.
    ls -N -F --color=auto --group-directories-first -Al "$@" | grep --color=auto ^d
}

$ lsd() { \ls -N -Al "$@" | \grep ^d ;}    # alias 적용을 방지하려면 escape 합니다.

$ declare -f lsd
lsd () 
{ 
    \ls -N -Al "$@" | \grep ^d
}
  • BASH_ALIASES associative array 를 통해서 alias 된 명령을 구할 수 있습니다.
$ alias p3='ps fo user,ppid,pid,pgid,sid,tty,stat,args'

$ echo "${BASH_ALIASES[p3]}"
ps fo user,ppid,pid,pgid,sid,tty,stat,args
  • alias 에는 함수처럼 인수를 전달할 수 없습니다.

  • 설정한 alias 를 escape 하려면 이름 앞에 \ 를 붙이거나 quote 을 하면 됩니다. 또한 command, builtin 명령을 사용하거나 명령 이름을 변수에 대입해 실행하면 alias 적용이 되지 않습니다.

  • 설정한 alias 를 삭제할 때는 unalias 명령을 사용합니다.

Alias 는 shell 환경에서만 사용할 수 있다.

다음과 같은 경우 ls 명령이 alias 되어 있어도 shell 환경에서 실행되는 것이 아니므로 alias 적용이 되지 않습니다.

# 다음의 ls -l 명령 스트링은 env, find, xargs 명령에 전달되어 
# env, find, xargs 명령이 직접 실행하는 것으로 alias 적용이 되지 않는다.

$ env ls -l

$ find -type f -exec ls -l {} \;

$ find -type f -print0 | xargs -0 -i ls -l {}

Alias 활용

readline 함수인 alias-expand-line 을 특정 키에 바운드 해놓으면 필요할 경우 명령 실행 전에 사용자가 직접 alias 를 확장해 수정할 수 있습니다. 프롬프트 상에 보이는 전체 명령문에 대해서 확장이 일어나므로 만약에 명령문에 heredoc 이 포함되어 있으면 heredoc 내용에서도 확장이 일어나므로 주의할 필요가 있습니다. 이럴 경우는 먼저 명령 라인을 확장해놓고 heredoc 내용을 붙여넣기 하면 됩니다.

~/.inputrc 파일에 추가할 내용

# Alt-space 키에 alias-expand-line 함수 설정
"\e ": alias-expand-line

# F7 키를 누르면 vi 에디터로 aliases.sh 파일을 수정
"\e[18~": "\C-k\C-uvi ~/.bashrc.d/aliases/aliases.sh\C-m"

# Shift-F7 키를 누르면 source 명령으로 alias 파일을 다시 읽어들임
"\e[18;2~": "\C-k\C-uunalias -a; for f in ~/.bashrc.d/aliases/*.sh; do . \"$f\"; done\C-m"

aliases.sh 파일 예제

alias ps3='ps fo user,ppid,pid,pgid,sid,tty,stat,args'
alias ps4='ps axfo user,ppid,pid,pgid,sid,tty,stat,args'
alias t='type -a'
alias watch='watch -d -n1'
alias .awk='awk '\''{a[$1]} END {for (i in a) print i}'\'
alias .strace='{ timeout 10 strace -f -p 1234 ;} |& vi -'

alias gccx='gcc -g -xc - <<\@ && ./a.out'
alias gcco='cat > z.c <<\@ && gcc -g -c z.c && objdump -S z.o | less; rm -f z.c z.o'
alias g++x='g++ -g -xc++ - <<\@ && ./a.out'
alias g++o='cat > z.cpp <<\@ && g++ -g -c z.cpp && objdump -SC z.o | less; rm -f z.cpp z.o'

# ~/.inputrc 에서 F2 키 바인딩. 
# 현재 명령행 상에서 작성 중인 내용과 함께 vi 에디터가 열립니다.
"\eOQ": edit-and-execute-command

----------------------------------------------------------------

# vi 에디터에서 자동으로 C/C++ highlight 하려면 다음 내용을 추가해 주면 됩니다.
# ~/.vimrc 파일에 추가
let myscriptsfile = "~/.vim/scripts.vim"

# ~/.vim/scripts.vim 파일내용

let s:line1 = getline(1)
let s:line2 = getline(2)
let s:line3 = getline(3)

if s:line1 =~# '\<(gccx\|gcco\|g++x\|g++o\|gcc\|g++\|clang\|clang++)\>'
    \ || s:line1 =~# '^\s*#include'
    \ || s:line2 =~# '^\s*#include'
    \ || s:line3 =~# '^\s*#include'
    set ft=cpp

elseif s:line1 =~# '\<g\?awk\>'              " awk
    set ft=awk

elseif s:line1 =~# '\<make .*<<\\'           " Makefile
    set ft=make

elseif s:line1 =~# '\<m4 .*<<\\'             " m4
    set ft=m4

elseif s:line1 =~# '\<python3\? .*<<\\'      " python
    set ft=python

elseif s:line1 =~# '\<node .*<<\\'           " node
    set ft=javascript

endif
unlet s:line1 s:line2 s:line3

ja, jb, jae, jbe 같은 above, below 는 unsigned jump 에 해당하고 jg, jl, jge, jle 같은 greater, less 는 signed jump 에 해당합니다. 위에서 보았듯이 두 오퍼런드 중에 하나라도 unsigned 이면 unsigned jump 가 사용됩니다. 이 말은 연산시 비트값을 unsigned 값으로 해석한다는 뜻으로 다음과 같은 경우도 연산 결과를 unsigned 값으로 해석하면 0 보다 크게 되므로 결과는 "no" 가 됩니다.

unsigned int aa = 200;                   int aa = 200;             int aa = 200;
unsigned int bb = 300;          unsigned int bb = 300;             int bb = 300;

if ((aa - bb) < 0)              if ((aa - bb) < 0)                 if ((aa - bb) < 0)
    puts("yes");                    puts("yes");                       puts("yes");
else                            else                               else
    puts("no");                     puts("no");                        puts("no");

# 결과 : "no"                    # 결과 : "no"                       # 결과 : "yes"

int 보다 작은 단위인 char, short 의 경우는 연산시 먼저 signed int 로 변경이되고 signed 비교를 하게됩니다. 그리고 연산 결과도 signed int 가 됩니다. 사실 CPU 에는 1 byte 크기의 char 나 2 bytes 크기의 short 연산기가 따로 없습니다. 최소 연산 단위는 4 bytes 크기의 int 입니다. 따라서 char, short 값이 연산될 때는 먼저 int 값으로 변환이 이루어지는데 이때 unsigned 값이면 비트값에 변경이 없지만 signed 값이면 음수 여부에 따라서 부호 비트값이 확장됩니다. 아래서 세 번째 예를들면 (aa < bb) 연산을 하기위해 char aa = -1 는 먼저 4 bytes int 로 변경되는데 이때 aa 는 signed 값이므로 부호 유지를 위해 char 형 -1 (0xff) 이 int 형 -1 (0xffffffff) 로 되고 이후에 unsigned int bb 와 unsigned 비교를 하게되면 결과는 no 가 됩니다.

unsigned short aa = 2;          unsigned char aa = 200;            char aa = -1; 
short bb = -1;                  short bb = 300;                    unsigned int bb = 2;

if (aa > bb)                    if ((aa - bb) < 0)                 if (aa < bb)
    puts("yes");                    puts("yes");                       puts("yes");
else                            else                               else
    puts("no");                     puts("no");                        puts("no");

# 결과 : "yes"                   # 결과 : "yes"                      # 결과 : "no"

Quiz

다음은 gcc 전처리기 출력에서 # 로 시작하는 라인을 제거하고 출력하기 위한 명령인데요.
test.h 파일명을 다른 이름으로도 사용할 수 있게 alias 설정을 하려면 어떻게 할까요?

$ gcc -E test.h | sed -n '/^# [0-9]\+ "/!p'

redirection 과 프로세스 치환을 활용하면 gcc -E 를 명령문 끝으로 옮길 수 있습니다.

$ alias gcpp='> >(sed -n '\''/^# [0-9]\+ "/!p'\'') gcc -E'

$ gcpp test2.h      # 파일명을 변경할 수 있다.

$ gcpp -            # stdin 에서 입력을 받음 
#define AA 100
AA
                    # Ctrl-d 로 종료
100                 # 결과

2.

java 는 버전 11 부터 .java 소스파일을 직접 실행할 수가 있게 되었습니다. 하지만 heredoc 으로는 실행을 할 수가 없는데요. ( 소스파일이 존재해야 합니다 ). heredoc 으로도 실행할 수 있게 alias 를 설정하려면 어떻게 할까요?

# java 가 실행 중에 비정상 종료될 경우에도 $tmp 파일이 삭제되려면 trap 을 설정해야 합니다.
$ alias jrun=$( cat <<\@
( tmp=`mktemp -p /dev/shm` && trap "rm -f $tmp" 0 && cat <<\@ > $tmp && java --source 20 $tmp )
@
)

$ jrun
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, Alias!");
    }
}
@
Hello, Alias!

---------------------------------------------------

# 다음과 같이 프로세스 치환을 사용할 경우 /dev/fd/63 는 파이프가 되기 때문에
# 일반 파일에서처럼 seek 을 할 수가 없어서 Illegal seek 오류가 발생합니다.
$ java --source 20 <( cat <<\@ 
public class Hello {
    public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
}
@
)
error: error reading source file /dev/fd/63: java.io.IOException: Illegal seek

다음은 golang 언어입니다.

$ alias grun=$( cat <<\@
( cd `mktemp -d -p /dev/shm` && trap "rm -rf '$PWD'" 0 && go mod init test 2>/dev/null && cat <<\@ > main.go && go mod tidy && go build && cd - && "$OLDPWD/test" )
@
)

$ grun
package main
import "os"
func main() {
    println("Hello Alias!")
    os.Exit(3)
}
@
Hello Alias!

$ echo $?
3

다음은 rust 언어입니다.

$ alias rrun=$( cat <<\@
( tmp=`mktemp -p /dev/shm` && trap 'rm -f $tmp' 0 && rustc -o $tmp - <<\@ && $tmp )
@
)

$ rrun
use std::process;

fn main() {
    println!("Hello Alias!");
    process::exit(3);
}
@
Hello Alias!

$ echo $?
3

3.

C++ 언어에서는 기본적으로 signed 값과 unsigned 값을 연산하면 올바른 값을 얻을 수 없는데요. signed, unsigned, float, double 어떤 수를 넣어도 연산이 되는 sum 함수를 만들려면 어떻게 할까요?

참조영상 : https://youtu.be/6P0V-m6s1fM

#include <iostream>

auto sum(auto a, auto b ) 
{
    return a + b;
}

int main() 
{
    short s = -10;                // signed 값과 unsigned 값을 연산하면
    unsigned int u = 5;
    std::cout << sum(s, u) << '\n';
}

------------------------------------

$ g++ -std=c++20 test.cpp 

$ ./a.out                         // 올바른 값이 출력되지 않는다.
4294967291

다음과 같이 템플릿 프로그래밍을 통해서 어떤 수를 넣어도 연산이 되는 함수를 만들 수 있습니다.

#include <iostream>

template <typename T> struct st_signed_type;
template <std::integral T>
struct st_signed_type<T> { using type = std::make_signed_t<T>; };
template <std::floating_point T>
struct st_signed_type<T> { using type = T; };
template <typename T> using make_signed_t = 
    typename st_signed_type<std::remove_cvref_t<T>>::type;

template <typename... Types> using common_signed_t = 
    make_signed_t< std::common_type_t<std::remove_cvref_t<Types>... >>;
template <typename T> concept arithmetic_c = std::is_arithmetic_v<std::remove_cvref_t<T>>;

auto sum(arithmetic_c auto a, arithmetic_c auto b )          // sum 함수
{
    using signed_common_t = common_signed_t<decltype(a), decltype(b)>;
    return static_cast<signed_common_t>(a) + static_cast<signed_common_t>(b);
}

int main() 
{
    short s = -10;                // signed, unsigned, float, double ...
    unsigned int u = 5;           // 어떤 수를 넣어도 올바른 값이 출력된다.
    float f = 5.5f;
    double d = -4.5;

    std::cout << sum(s, u) << '\n';
    std::cout << sum(s, f) << '\n';
    std::cout << sum(u, d) << '\n';
    std::cout << sum(f, d) << '\n';
}

-------------------------------------

$ g++ -std=c++20 test.cpp 

$ ./a.out 
-5
-4.5
0.5
1

signed, unsigned, float, double 값들의 최소값 구하기

. . .

auto min(arithmetic_c auto a, arithmetic_c auto b) 
{
    using signed_common_t = common_signed_t<decltype(a), decltype(b)>;
    return  static_cast<signed_common_t>(
                (signed_common_t) a < (signed_common_t) b ? a : b );
    // return a < b ? a : b;   // 이 방법은 1.1 이 출력된다.
}

auto min(arithmetic_c auto a, arithmetic_c auto b, arithmetic_c auto... args)
{
    return min( min(a, b), args...);
}

int main()
{
    auto minimum = min(4u, -3, 1.8, 3.14f, 1.1, 9);
    std::cout << minimum << '\n';
}

-------------------------------------

$ g++ -std=c++20 test.cpp 

$ ./a.out 
-3