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 보다 작은 단위인 (unsigned) char, (unsigned) 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 XXXXX.java` && trap "rm -f $tmp" 0 && cat <<\@ > $tmp && java $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 2>&1` && trap "rm -rf '$PWD'" 0 && { go mod init grun && cat <<\@ > main.go && go mod tidy && go build ;} &> /dev/null && cd - && "$OLDPWD/grun" )
@
)
$ grun
package main
import "os"
func main() {
println("Hello Alias!")
os.Exit(3)
}
@
Hello Alias!
$ echo $?
3
다음은 rust 언어입니다.
$ alias rrun=$( cat <<\@
( cd `mktemp -d -p /dev/shm 2>&1` && trap "rm -rf '$PWD'" 0 && { cargo new rrun && cd rrun && cat <<\@ > src/main.rs && cargo build --release ;} &> /dev/null && cd - && "$OLDPWD/target/release/rrun" )
@
)
$ seq 100 | rrun
use std::io::{self, BufRead};
fn main() {
let mut vec = Vec::new();
for line in io::stdin().lock().lines() {
if let Ok(input) = line {
vec.push(input.parse::<i32>().unwrap());
}
}
println!("{}", vec.iter().sum::<i32>());
}
@
5050
$ grun | rrun
package main
import "fmt"
func main() {
for i := 1; i <= 100; i++ {
fmt.Println(i)
}
}
@
use std::io::{self, BufRead};
fn main() {
let vec: Vec<i32> = io::stdin().lock().lines().flatten()
.map(|line| line.parse().unwrap()).collect();
println!("{}", vec.iter().sum::<i32>());
}
@
5050
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