理系学生のちらしの裏

日記だったり雑記だったり

【競プロ】どのディレクトリからでもすぐに競プロできるようなシェルスクリプトを書いた

いつでもどこでもすぐに競プロの問題解き始められるようなシェルスクリプトを書きました。イメージとしては、サイト名、コンテスト名、問題名を入力すると対応するディレクトリに移動して、テンプレファイルからC++ファイルを作成、問題のサンプルテストケースを取得してinファイルに書き込む、といった感じです。

コードはこちら

https://github.com/hpano/CompetitiveProgramming/blob/master/compp.sh

実際の動作はこんな感じ。全然関係ないディレクトリから、AtcoderのABC173のディレクトリに移り、A問題とB問題のサンプルテストケースを取得しています。

f:id:Pano:20200906160518g:plain

この問題のコンテストページはこちら

atcoder.jp


さてコードを見ていきましょう。まずは入力部分です。

ここでは、-dオプションの後に続けられたものをディレクトリ名として配列directorys、-fオプションの後に続けられたものを作成したいファイル名として配列filesに突っ込んでいます。

while test "$1" != ""; do
  case $1 in
    -*)
      if [[ "$1" =~ 'h' ]] || [[ "$1" =~ '-help' ]]; then
        hflag=true
      elif [[ "$1" =~ 'd' ]] || [[ "$1" =~ '-directory' ]]; then
        while [[ $2 != -* ]] && [[ $2 != "" ]]; do
          directorys+=($2)
          shift
        done
      elif [[ "$1" =~ 'f' ]] || [[ "$1" =~ '-file' ]]; then
        while [[ $2 != -* ]] && [[ $2 != "" ]]; do
          if [[ $2 =~ "." ]] && [[ $2 != *.cpp ]]; then
            echo "[compp] Invalid file $2 !"
            echo "        You can input *.cpp or *"
            exit 1
          fi
          files+=(${2%.cpp})
          shift
        done
      else
        echo "[compp] Invalid option $1 !"
        usage
        exit 1
      fi
      shift
      ;;
    *)
      echo "[compp] Invalid input $1 !"
      usage
      exit 1
      ;;
  esac
done

directorys=(${directorys[@]//// })


続いて競プロ用のルートディレクトリのパスを調べます。どこからでもbashファイル実行できるようにこのディレクトリのパスを通してあるので、whichコマンドでパスを取得します。一応パスを通していなくても使えるよう、その場合はパスの入力を求めます。ここで"n"を入力すると、今いるディレクトリを競プロ用のルートディレクトリとみなします。

declare rootpath=$(which compp.sh)
if [[ ${rootpath} == "" ]]; then
  read -p "[compp] Input competitive programing directory's root path or 'n' to give up cd: " rootpath
fi
if [[ ${rootpath} == "n" ]]; then
  rootpath=$(pwd)
elif [[ ${rootpath: -3} == ".sh" ]]; then
  rootpath=${rootpath%\/*}
fi


そしてそのディレクトリに移動し、目的のディレクトリまでさらに移動します。競プロ用のルートディレクトリから順に、directorysに入っているディレクトリを辿っていき(なければ作成するか選択でき)、たどり着いた場所にファイルを作成するといった感じです。

declare yn
cd ${rootpath}
for dir in ${directorys[@]}; do
  if [[ -d ${dir} ]]; then
    cd ${dir}
  else
    read -p "[compp] Directory '${dir}' not exists. make? [y/n]: " yn
    if [[ ${yn} == "y" ]]; then
      mkdir ${dir} && cd ${dir}
    fi
  fi
done
echo "[compp] Now at $(pwd)"


最後に前回の記事で作成したadd.shを使い、配列filesに入っているファイルを作成します。こちらもパスが通っていなければ、add.shのパスの入力を求めます。

declare addpath=$(which add.sh)
if [[ ${addpath} == "" ]]; then
  read -p "[compp] Input add.sh path or 'n' to give up add file: " addpath
fi
if [[ ${addpath} == "n" ]]; then
  exit 1
elif [[ ${addpath: -3} != ".sh" ]]; then
  if [[ ${addpath: -1} != "/" ]]; then
    addpath+="/"
  fi
  addpath+="add.sh"
fi

${addpath} ${files[@]}


このシェルスクリプトの中でcdをしてディレクトリの移動をしているのですが、シェルスクリプトは基本的には現在のシェルとは別のシェルで実行されるので、普通に実行しただけだと現在のシェルを移動させることはできません。実行する際に.コマンドかsourceコマンドを使用する必要があります。

こちらのサイトが参考になりました。

www.pc-gear.com

毎回.を打つのも忘れそうだしめんどくさいので、~/.bashrcに.コマンドありでこのシェルスクリプトを実行する関数を用意しました。

# competitive programing by hpano
compp(){
    . compp.sh $@
}

これで実行する際にはcompp -d [ディレクトリ] -f [ファイル]と入力することで、どこからでも実行できるようになりました。


また、ここまでで色々パスを通したり、.bashrcに書き込んだり、シェルスクリプトパーミッションを変更したりと色々設定があって忘れそうなので、それら設定を一括で行えるsetting.shを作成しました。

https://github.com/hpano/CompetitiveProgramming/blob/master/setting.sh

#/bin/bash

function write2bash_profile() {
  local bashpro=$(cat ~/.bash_profile)
  if [[ !(${bashpro} =~ "# competitive programing by hpano") ]]; then
    local yn
    read -p "[setting] Write PATHs to ~/.bash_profile now? [y/n]: " yn
    while [[ ${yn} != "y" ]] && [[ ${yn} != "n" ]]; do
      read -p "Answer only y or n [y/n]: " yn
    done
    if [[ ${yn} == "y" ]]; then
      echo "# competitive programing by hpano" >> ~/.bash_profile
      echo "export PATH=\$PATH:${dirpath}" >> ~/.bash_profile
      source ~/.bash_profile
      echo "[setting] Wrote PATHs to ~/.bash_profile"
    fi
  fi
}

function write2bashrc() {
  local bashrc=$(cat ~/.bashrc)
  if [[ !(${bashrc} =~ "# competitive programing by hpano") ]]; then
    local yn
    read -p "[setting] Write functions to ~/.bashrc now? [y/n]: " yn
    while [[ ${yn} != "y" ]] && [[ ${yn} != "n" ]]; do
      read -p "Answer only y or n [y/n]: " yn
    done
    if [[ ${yn} == "y" ]]; then
      echo "# competitive programing by hpano" >> ~/.bashrc
      echo "compp(){" >> ~/.bashrc
      echo "    . compp.sh $@" >> ~/.bashrc
      echo "}" >> ~/.bashrc
      source ~/.bashrc
      echo "[setting] Wrote functions to ~/.bashrc"
    fi
  fi
}

declare dirpath=$(dirname $0)

write2bash_profile
write2bashrc
chmod +x ${dirpath}/setting.sh
chmod +x ${dirpath}/add.sh
chmod +x ${dirpath}/compile.sh
chmod +x ${dirpath}/compp.sh
chmod +x ${dirpath}/remove.sh
chmod +x ${dirpath}/run.sh

echo "[setting] Finish setting"


これでますます競プロが捗るはずです。頑張って精進します。