これだけ知っておけばgitには困らない
- これだけ知っておけばgitには困らない
- 想定環境
- git設定編
- 運用編
- Fork
- git clone
- upstreamの設定、確認(git remote add upstream $URL)
- ローカルブランチの作成、originへの紐づけ(git checkout -b develop, git push -u origin develop)
- すでにoriginにブランチが存在する場合は、以下のように設定することも可能
- commit, push (git commit -m "comment", git push)
- upstraemの更新を取得(=originを最新化)(git fetch -p upstream, git merge upstream/develop)
- コンフリクトの解消 (解消後にmerge commit)
- pull request作成(gh pr create)
- よく使うコマンド集
- ブランチ削除(local: git branch -D develop , remote: git push -d origin develop)
- ローカルブランチをupstreamの最新で更新
- commitの取り消し
- ブランチ存在確認
- gitignoreが反映されない場合(キャッシュの削除)
- 特定のファイルの変更を取り消す
- 変更したファイルの一覧(--name-only)
- フォーマットしてログ出力(でもログの確認は基本的にトータスとか使う方が良いと思う)
- aliasの設定
- 設定ファイルの場所
- 強制push(非推奨)
- 変更の一時保存、取り出し(git stash save "comment", git stash list, git stash apply)
- なんか変なことになったらとりあえずこれでバックアップをとる
- helpの確認
- commit objectの指定方法いろいろ
- そのうち追記予定(そこまで使用頻度は高くないけどたまに使うものたち)
これだけ知っておけばgitには困らない
普通に開発する分ならここに記載したコマンド群(オプション含め)で大抵のことは事足りると思います。
随時更新予定 更新日 2022/11/23
想定環境
OS: windows git version 2.37.3.windows.1 remote: github github-flowを使用(forkしてPull Requestする運用方法) github-cliを使用(ただし別に使わなくても良い) コマンドプロンプト(cmd.exe)での使用
git設定編
gitの設定がlocal,global,systemの3つの階層があることは重要なので理解しておいでください。
※私は基本的にlocalとglobalしかいじりません。
user, mail設定
コマンドでも可能ですが、私は設定ファイルを直接いじることが多いです。
1. %userprofile%\.gitconfig
を開く(ファイルがなければ作成する 文字コード: utf8
)
2. 以下の通り追加
[user] name = user_username email = user_email@mail.com
自動改行を無効か
デフォルトだとpullとかpushとかしたときにgitが勝手に改行コードをOSに合わせて変更するようになっているので、それを無効化します。
(つまりgitが勝手に改行コードを変更したりしなくなります)
[core] autoCRLF = false
エディタの設定
デフォルトだとviになっているので、使い慣れたエディタに変更する。
※私はサクラエディタを使います。
※exeファイルへの絶対パスを指定するか、パスを通している場合は以下のように実行ファイル名のみの指定も可能。
[core] editor = sakura
ssh鍵の作成、登録
- 公開鍵、秘密鍵の作成
$ssh-keygen -t ecdsa C:\develop\WHAT_IS\what_is_help>ssh-keygen -t ecdsa Generating public/private ecdsa key pair. Enter file in which to save the key (C:\Users\user_name/.ssh/id_ecdsa): #何も入力せずEnter Enter passphrase (empty for no passphrase): #何も入力せずEnter Enter same passphrase again: #何も入力せずEnter Your identification has been saved in C:\Users\user_name/.ssh/id_ecdsa. #こっちが秘密鍵 Your public key has been saved in C:\Users\user_name/.ssh/id_ecdsa.pub. #こっちが公開鍵 The key fingerprint is: SHA256:gdskjgijsidjsjie545as4d5g4asg user_name@HOSTNAME The key's randomart image is: +---[ECDSA 256]---+ |o+ o+. | |+ + o =+. | +----[SHA256]-----+
- githubに公開鍵を登録
github.comにログインし、settings
⇒SSH and GPG keys
⇒new SSH Key
title
: 任意の名前を登録(例えばPCのホスト名とか)
key type
: Authentication Key
Key
: 先ほど作成したキー(C:\Users\user_name/.ssh/id_ecdsa.pub)をテキストエディタで開き、中身をコピーして貼り付け
[Add SSH Key]押下
これでgithubにSSH接続が可能です(要するにpushとかできるようになる)
TortoiseGitの場合
- TortoiseGit\bin\puttygen.exeを実行
- Generate⇒枠内でマウスを適当に動かすと鍵が作成される。
- [Save public key]押下⇒C:\Users\user_name/.ssh/tortoise_puttyに保存
- [Save public key]押下⇒C:\Users\user_name/.ssh/tortoise_putty.ppkに保存
- githubに公開鍵を登録 ※登録手順は上記の「githubに公開鍵を登録」と同様 ※C:\Users\user_name/.ssh/tortoise_puttyの中身をgithubに登録
- Pagentに登録
cd TortoiseGit\bin pageant.exe "C:\Users\user_name\.ssh\for_tortoise_git.ppk"
これでTortoiseGitを使用してgithubにSSH接続が可能です(要するにTortoiseGitでpushとかできるようになる)
運用編
Fork
こちらを使っていきます。
https://github.com/monkey999por/TestRep
github上で操作フォークしたいリポジトリでforkボタン押下⇒[Create fork]
これで自分のリポジトリにフォークが作成されます。
git clone
$git clone git@github.com:monkey999por-sub/TestRep.git $cd TestRep
upstreamの設定、確認(git remote add upstream $URL)
$git remote add upstream https://github.com/monkey999por/TestRep.git #正しく設定されているか確認 #origin: forkしたリポジトリ #upstream: fork元のリポジトリ $git remote -v origin git@github.com:monkey999por-sub/TestRep.git (fetch) origin git@github.com:monkey999por-sub/TestRep.git (push) upstream https://github.com/monkey999por/TestRep.git (fetch) upstream https://github.com/monkey999por/TestRep.git (push) #リモートブランチの確認 origin/mainとupstream/mainがあればOK $git fetch -p upstream $git branch --remote origin/HEAD -> origin/main origin/main upstream/main
ローカルブランチの作成、originへの紐づけ(git checkout -b develop, git push -u origin develop)
$git checkout -b develop Switched to a new branch 'develop' $git push -u origin develop Total 0 (delta 0), reused 0 (delta 0), pack-reused 0 remote: remote: Create a pull request for 'develop' on GitHub by visiting: remote: https://github.com/monkey999por-sub/TestRep/pull/new/develop remote: To github.com:monkey999por-sub/TestRep.git * [new branch] develop -> develop branch 'develop' set up to track 'origin/develop'. #ちゃんと紐づいているか確認 $git branch -vv * develop 4f11259 [origin/develop] comment
すでにoriginにブランチが存在する場合は、以下のように設定することも可能
$git branch -u origin/develop develop branch 'develop' set up to track 'origin/develop'.
commit, push (git commit -m "comment", git push)
$git add . $git commit -m "originで追加" $git push
upstraemの更新を取得(=originを最新化)(git fetch -p upstream, git merge upstream/develop)
#upstreamの更新情報を取得 $git fetch -p upstream #自分のローカルブランチにupstreamを取り込み $git merge upstream/main Auto-merging file.txt CONFLICT (content): Merge conflict in file.txt Automatic merge failed; fix conflicts and then commit the result.
あらら、コンフリクトしちゃいました。↓
コンフリクトの解消 (解消後にmerge commit)
#状況確認 $git status On branch develop Your branch is up to date with 'origin/develop'. You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add <file>..." to mark resolution) both modified: file.txt #これがコンフリクトしてる
コンフリクトしているファイルを開き、修正する。
<<<<<<< HEAD originで追加したもの ======= upstreamで追加されていたもの >>>>>>> upstream/main
<<<<<<< HEAD
と=======
の間: HEADから追加しようとした行
=======
と>>>>>>> upstream/main
の間: upstream/mainで追加されていた行
修正方法としては<<<...
と===...
と>>>...
を削除して、中身をいい感じに結合なり書き換えなりします。今回はどちらも残すようにしました。
修正後 originで追加したもの upstreamで追加されていたもの
コミットする
$git add file.txt $git commit -m "merge commit" $git push $git l #git logにalias設定してます。詳細は後述 * [2022-11-20 6 minutes ago] monkey999por-sub e94ec15 (HEAD -> develop, origin/develop)merge commit |\ | * [2022-11-20 27 minutes ago] monkey999por 61bc096 (upstream/main)upstreamで追加 * | [2022-11-20 25 minutes ago] monkey999por-sub 0974599 originで追加
pull request作成(gh pr create)
$gh pr create ? Which should be the base repository (used for e.g. querying issues) for this directory? monkey999por/TestRep # upstreamに設定したリポジトリを選択 Creating pull request for monkey999por-sub:develop into main in monkey999por/TestRep ? Title first pull request ? Body <Received> ? What's next? Submit https://github.com/monkey999por/TestRep/pull/1
ここまでで基本的にやることは終わり
よく使うコマンド集
ブランチ削除(local: git branch -D develop , remote: git push -d origin develop)
ローカルブランチの削除
$git branch -d branch_name
リモートブランチの削除
$git push -d origin branch_name
ローカルブランチをupstreamの最新で更新
$git fetch -p upstream $git reset --hard upstream/branch_name
commitの取り消し
$git commit -m "dummy commit" #間違えてコミットしちゃった #取り消し $git reset --mixed head~
ブランチ存在確認
$git branch -a | find /i "branch_name"
gitignoreが反映されない場合(キャッシュの削除)
こちらの記事が参考になります
https://qiita.com/fuwamaki/items/3ed021163e50beab7154
特定のファイルの変更を取り消す
$git checkout filename.txt #ワイルドカード指定も可能 $git checkout *.txt
変更したファイルの一覧(--name-only)
$git diff --name-only
フォーマットしてログ出力(でもログの確認は基本的にトータスとか使う方が良いと思う)
$git log --graph --all --format='[%as %ah] %x09%C(cyan bold)%an%Creset%x09%C(yellow)%h%Creset %C(green reverse)%d%Creset%s' --branches
aliasの設定
%userprofile%\.gitconfig
にこんな感じで追記。
[alias] a = add b = branch c = checkout ch = cherry-pick d = diff rmh = reset --mixed head rhh = reset --hard head s = status f = fetch l = log --graph --all --format='[%as %ah] %x09%C(cyan bold)%an%Creset%x09%C(yellow)%h%Creset %C(green reverse)%d%Creset%s' --branches
設定ファイルの場所
local: プロジェクトフォルダの中の.git/config
global: %userprofile%.gitconfig
system: インストールディレクトリのetc/gitconfig
それぞれ以下で開くこともできる
$git config --edit --local $git config --edit --global $git config --edit --system
強制push(非推奨)
※ブランチの関係性が壊れる可能性があるのでよほどのことがない限り使わないように。
$git push -f
変更の一時保存、取り出し(git stash save "comment", git stash list, git stash apply)
#ファイルを新規に作ったりしている場合は最初にステージングに乗せておく必要あり $git add . $git stash save "comment" #stashの確認 $git stash list stash@{0}: On main: comment #stashの取り出し $git stash apply stash@{0} #stashの削除 $git stash drop stash@{0} Dropped stash@{0} (88ecdc38b520e4f0b67a78bf18d1623606465964)
なんか変なことになったらとりあえずこれでバックアップをとる
例えばupstream取り込みmergeしてコンフリクト置きまくったときとか。なれないと焦ると思うが、いったん以下のことだけしておけばバックアップはとれる。
#いったんすべてステージングに追加(新規追加したファイルとかもこれで対応可能) $git add . #stashに保存 $git stash save "comment" #保存されていることを確認する $git stash list #upstreamの最新を取得 $git fetch -p upstream main $git reset --hard upstream/main #stashを適用 $git stash apply stash@{n} ※n #ここでコンフリクトの解消する #あとは普通にコミットしてPRの作成 $git add . $git commit -m "merge commit" $git push $gh pr create
helpの確認
$git comand -h #例 $git status -h usage: git status [<options>] [--] <pathspec>... -v, --verbose be verbose -s, --short show status concisely -b, --branch show branch information --show-stash show stash information --ahead-behind compute full ahead/behind values --porcelain[=<version>] machine-readable output --long show status in long format (default) -z, --null terminate entries with NUL -u, --untracked-files[=<mode>] show untracked files, optional modes: all, normal, no. (Default: all) --ignored[=<mode>] show ignored files, optional modes: traditional, matching, no. (Default: traditional) --ignore-submodules[=<when>] ignore changes to submodules, optional when: all, dirty, untracked. (Default: all) --column[=<style>] list untracked files in columns --no-renames do not detect renames -M, --find-renames[=<n>] detect renames, optionally set similarity index
commit objectの指定方法いろいろ
head~ #headに対して1つ前のコミット head~~ #headに対して2つ前のコミット head~~~ #headに対して3つ前のコミット stash@{0} #stashに保存したもの。使うことはないだろうけどここに対してcheckoutも可能 6ed25aa #logとかで確認可能なcommit hash ------- branch #ローカルブランチ origin/branch #originのブランチ upstream/branch #upstreamのブランチ tag #タグ
そのうち追記予定(そこまで使用頻度は高くないけどたまに使うものたち)
【java】ファイルの作成、読み込み、書き込み、削除のやり方
ファイルの作成、読み込み、書き込み、削除のやり方
static void fileOperation() throws IOException { // 作成 // create file String filename = "C:\\temp\\file.txt"; File outFile = new File(filename.toString()); boolean ready = outFile.exists() && outFile.delete(); if (ready) Files.createFile(Path.of(filename)); // 書き込み FileWriter writer = new FileWriter(filename); String br = System.getProperty("line.separator"); writer.write("aaaa" + br); writer.write("bbbb" + br); writer.write("cccc" + br); // バッファを書き込み 普通はcloseすれば書き込まれるから不要 writer.flush(); // 読み込み 全行 String contents = Files.readString(Paths.get(filename)); // aaaa // bbbb // cccc System.out.println(contents); // 読み込み 1文字 FileReader fileReader = new FileReader(outFile, StandardCharsets.UTF_8); System.out.println(fileReader.read());// 一文字読み込み 97 ※aの文字コード // 読み込み 1行 BufferedReader rowReader = new BufferedReader(fileReader); String content; while ((content = rowReader.readLine()) != null) { // aaa(※↑同じファイルに対してraedとかすると、その分はすでに読み込まれたことになるっぽい。なので↓の出力はaaa) // , bbbb, cccc System.out.println(content); } // try with resourceだとこんな感じ closeは不要 try (FileReader f = new FileReader(filename); BufferedReader b = new BufferedReader(f)) { System.out.println(b.readLine()); // aaaa } catch (Exception e) { } // close try { writer.close(); } catch (IOException e) { try { writer.close(); } catch (Exception e1) { } } }
ES2015以降のclassやオブジェクトリテラルの記法
ES2015以降のclassやオブジェクトリテラルの記法
※今のとこ雑にしかまとめてないですがどんどん整理していきます。
概要
class
は内部的にはfunction()
のシンタクスシュガー
javascript本来のprototypeベースのオブジェクト指向を疑似的にclassで内包しているだけ
※ただしfunction
と違いclass
は定義前には呼び出せない(要するにnew
演算子はclass定義より後にしか使えない)
クラス(class), アクセサ(get,set)
class Member { // constructor constructor(value) { // field this.value = value; } // get accesser // _temp:内部的にのみ持つ一時的な変数。 // valueにsetterで直接アクセスできないため、 // setterでは_tempに値をセットしている。そのため、getterでも_tempを返す。 // 一時変数名がなんでもよいことを明示的にするために名前を_tempとしているが、本来は_value(アンダースコア + アクセサ名)にすべき // こういうこと https://ginpen.com/2017/12/05/javascript-getter-setter/ get value() { return `${this._temp} : call get`; } // set accesser // _temp:内部的にのみ持つ一時的な変数。 // なぜvalueではなく_tempを返すのか? // ⇒仮にthis.value = valueとしてしまうと。this.valueにセットしようとした時点でset valueアクセサを介してしまう // ため、setが無限回呼ばれることになってしまう。 set value(_temp) { this._temp = `${_temp} : call set`; } // static method static staticMethod() { console.log(`this is static method`); } // instance method someMethod() { return this.value; } } let m = new Member("aaa"); // ※以下のような結果になる理由 // 1. new Member('aaa'); ⇒これの中に定義してるthis.value = valueが // setter(set value)を呼ぶ // 2. m.valueがgetter(get value)を呼ぶため console.log(m.value); //"aaa : call set : call get" console.log(m.someMethod()); // "aaa : call set : call get" Member.staticMethod(); // "this is static method"
classを変数に入れることも可能
const Test = class { // 中身は同じ };
継承(extends)
class Animal { constructor(value) { this.animalV = value; } getValue() { return `${this.animalV} (get with animal)`; } getAnimalValue() { return `${this.animalV} URYYYY`; } } class Cat extends Animal { constructor(animalV, value) { // 親コンストラクタ呼び出し super(animalV); this.catV = value; } // override getValue() { // 親のプロパティはthisでアクセス // 親のメソッドはsuperでアクセス return `${this.animalV} override by cat : ${ this.catV } : ${super.getValue()}`; } } const cat = new Cat("あにまる", "ねーこ"); console.log(cat.getValue()); // "あにまる override by cat : ねーこ : あにまる (get with animal)" console.log(cat.getAnimalValue()); // "あにまる URYYYY"
オブジェクトリテラル(object literal)
let objC = { firstName: "takashi", lastName: "honda", toString() { return `${this.firstName} : toString`; }, oldFunc: () => {console.log('古い書き方');} }; console.log(objC.toString()); // "takashi : toString" objC.oldFunc(); // "古い書き方"
プロパティの動的生成
let i = 0; let dynamicVars = { name: "test", ["a" + ++i]: "memo1", ["a" + ++i]: "memo2", ["a" + ++i]: "memo3", }; console.log(dynamicVars.a1); // memo1 console.log(dynamicVars.a2); // memo2
モジュール(export/import)について
いろいろ書くよりこれ見るのが一番
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/export
プライベート変数 (Private class fields)
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Classes/Private_class_fields
class ClassWithPrivateField { // private field #privateField; constructor() { this.#privateField = 42; // delete this.#privateField; // Syntax error // this.#undeclaredField = 444; // Syntax error } getPrivateField() { return this.#privateField; } } //instance.#privateField === 42; // Syntax error const instance = new ClassWithPrivateField(); console.log(instance.getPrivateField()); // 42
イテレータ(Iterator)
例えばfor of
とかは内部的にこの仕組みを使ってる
const ary = [1, 2, 3]; const itr = ary[Symbol.iterator](); let d; while ((d = itr.next())) { if (d.done) break; console.log(d.value); // 1,2,3 }
もしiteratbleなクラスを自作したいならこういうのが参考になる
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Iteration_protocols
ジェネレータ(Generator), yieldキーワード
function* myGen() { // function「*」でジェネレータになる let val = "aaa"; yield val; // yield: myGenの呼び出しごとに処理を一時停止し、myGenが呼ばれると次のyieldまで実行される val += " add after"; yield val; yield "ccc"; } console.log(myGen().next().value); // aaa for (const iterator of myGen()) { console.log(iterator); // "aaa","aaa add after","ccc" } // ### プロキシ(Proxy) const dataP = { red: "赤色", yellow: "黄色", }; const proxy = new Proxy(dataP, { get(target, prop) { console.log(target); // 対象のオブジェクト(=dataP) 例:{red: '赤色', yellow: '黄色'} console.log(prop); // 例:呼び出し側でproxy.redとした場合は、"red" return prop in target ? target[prop] : "?"; }, }); console.log(proxy.red); // "赤色" console.log(proxy.aaa); // "?" proxy.blue = "青色"; console.log(proxy.blue); // "青色"
参考
最近思うけどjavaのsetterがvoidなのって失敗だよなぁ
getterに関して思うこともありますが、それはまたいつか
javaのsetterメソッドって、こんな感じで自クラスを返した方が良かったんじゃないか?というお話
class Main { public static void main(String[] args) throws Exception { Sub sub = new Sub() .setValue1("1") .setValue2("2"); } } class Sub{ private String value1; private String value2; public Sub setValue1(String value1) { this.value1 = value1; return this; } public Sub setValue2(String value2) { this.value2 = value2; return this; } }
そもそもsetterなのに自クラスを返すのは、メソッドの責任範囲がおかしいとか、
java Bean仕様に則ってないのでよくないとかいろいろあるとは思います。が、ですね
オブジェクト指向ってそもそも内包されているデータに対してメソッドで操作を行うものじゃないですか
だから、「setterを使った後は、今セットした値に対して、処理を行うメソッドを呼び出す」という操作は必ず発生すると思うのですね
逆に、setter呼んだのに何もしない、っていうのは無く、もしそんな状況があったとしたら多分設計がおかしいと思うのですよ
まあそんな根拠です。
setterはpublic void setXX()
っていうのはもうデファクトスタンダードですし、Bean仕様なくらいなので今更どうすることもできないのですが
cmdでwhereして見つかったものののパスをクリップボードに保存したい
タイトル通りです
調べるのも面倒なので作りました。
何かと便利(意外とよく使う)
get.bat
@echo off @chcp 65001 > nul rem "パスが通ってるファイルのフォルダパスをクリップボードにコピーする" if "%1"=="" ( echo パスが通ってるファイルのフォルダパスをクリップボードにコピーする exit /B 0 ) where /Q %1 if "%ERRORLEVEL%"=="0" ( setlocal ENABLEDELAYEDEXPANSION for /F "delims=" %%i in ('where %1') do ( echo %%~dpi set /P <NUL="%%~dpi" | clip ) endlocal ) else ( echo ファイルが見つかりません )
■動かしみる
C:\>echo dummy|clip C:\>powershell Windows PowerShell Copyright (C) Microsoft Corporation. All rights reserved. Try the new cross-platform PowerShell https://aka.ms/pscore6 PS C:\> Get-Clipboard dummy PS C:\> exit C:\>where sakura C:\Program Files (x86)\sakura\sakura.exe C:\>get sakura C:\Program Files (x86)\sakura\ C:\>powershell Windows PowerShell Copyright (C) Microsoft Corporation. All rights reserved. Try the new cross-platform PowerShell https://aka.ms/pscore6 PS C:\> Get-Clipboard C:\Program Files (x86)\sakura\ PS C:\>
バッチはオーバーヘッドが少ないのでやっぱりいいですね。 Powershellももう少し気楽に使えるようになることを願います。
コマンドプロンプトでtasklistのヘルプ見たら、日本語と英語で違ってた
※提供されている機能が違うという話ではなく、単に日本語版のヘルプの説明が間違ってたという話です
■比較
分かりづらいですが、日本語版の方はつづりを間違えています。このヘルプ通りにしても動きません。
こういうのって報告すべきなのかな。
【Ubuntu】rmとかmvとか直接使うの怖いからwindowsのごみ箱的なコマンド作った
できたものがこちら。
github.com
バックアップとってから削除 or 移動します。
少し説明しますと、
1.ファイル名に日付を付与して、もともとの階層がわかるようにしてバックアップ
2.対象の削除 or 移動
3.バックアップ後の表示
という感じです。
#!/bin/bash # like a windows dust box # if delete file or directory, move dust box directory function dust () { local here=$(pwd) if [ -z ${DUSTBOX} ]; then echo not defined '$DUSTBOX' return elif [ -z ${1} ]; then echo "it has no argument" echo "syntax: \$dust {File or Directory}" return fi #バックアップするパスを取得 #echo $(dirname $(pwd)${1} |sed "s/^\///g") local backup_path=$(dirname ${1}) for p in ${backup_path//\/ }; do cd $p done #バックアップ先ディレクトリ backup_path=${DUSTBOX}/$(pwd | sed "s/^\///g" ) mkdir -p ${backup_path} #削除対象 local remove_target="$(pwd)/$(basename ${1})" echo "remove target: ${remove_target}" # back up file name local bk_file_name="$(basename ${remove_target}_$(date "+%Y_%m_%d_%H_%M_%S"))" echo "back up: ${backup_path}/${bk_file_name}" if [ -h "${1}" ]; then cp --attributes-only "${1}" "${DUSTBOX}/" rm -f "${1}" elif [ -f "${remove_target}" ]; then cp -f "${remove_target}" "${backup_path}/${bk_file_name}" rm -f "${remove_target}" elif [ -d "${remove_target}" ]; then cp -rf "${remove_target}" "${backup_path}/${bk_file_name}" rm -r "${remove_target}" else echo -e "can't delete this. it is not file or directory or symbolic link.\nuse \"rm\" command." fi echo -e "\n" tree ${backup_path} cd "${here}" }
実行結果はこんな感じ