20200921のMacに関する記事は6件です。

Mac が勝手に複製した "hoge 2.txt" 的なファイルを一括削除する

"hoge 2.txt"、それは突然現れた

僕、Github の private リポジトリに高専時代・大学時代の歴史をバックアップしてるんですけど、そのローカルファイルを iCloud Drive にも保存してたんですね。
今日ファイルの中身が見たくなって雲のマーク「ぽちー」って押して、ローカルに落としたんです。

一通り閲覧して満足したので、一応変な diff が無いか確認するために git status を叩きました。

すると「ズラズラズラーーーーーーー」っと無数にアウトプットが。。。。
しかも全部 unstaged files なんですよ。

「怖いなー、怖いなー。何も追加してないのになー。」
って思ってよく見ると、全部「hogehoge 2.pdf」みたいに、 "2" っていうのが拡張子の直前に付いてたんです。。。

それはなんと、全部 Mac が勝手に複製したファイルだったんです。。。。

対処法

はい。ということで、真面目に対処法をシェアします。
症状としては勝手にファイルが複製されて、全部拡張子の直前に 2 が付いている状態です。
例: hoge.txt の複製ファイルとして hoge 2.txt が作られている。

今回はこの、 2 とついたファイルだけを削除するワンライナーを紹介します。
結論から言うと、

$ find . -maxdepth 10 -type f | grep " 2\.*"| sed -e 's/ /\\ /g' | xargs rm

というコマンドを叩けばいいです。
お疲れ様でした。

。。。で終わるのもなんか味気ないので、軽く解説します。

解説

ワンライナーをもう一度。

$ find . -maxdepth 10 -type f | grep " 2\.*"| sed -e 's/ /\\ /g' | xargs rm

これをパイプ毎に分けると、

  1. 深さを指定して列挙: find . -maxdepth 10 -type f
  2. 2. と付くファイルのみに絞り込み: grep " 2\.*"
  3. スペースをエスケープ: sed -e 's/ /\\ /g'
  4. 削除: xargs rm

一つづつ解説します。

1. 深さを指定して列挙: find . -maxdepth 10 -type f

これは特に問題ないかと思います。
今回は下階層全て検索したかったので、 -maxdepth 10 を付けて 10 階層下まで見に行くようにしています。

find .find ${path/to/directory} とすれば好きなディレクトリについて検索できます。

2. 2. と付くファイルのみに絞り込み: grep " 2\.*"

ここも特筆することはないです。

. はそのままだとエスケープが必要なので、バックスラッシュを付けて \. としています。
これで ${半角スペース}2. をファイル名に含むものだけ抽出できます。

3. スペースをエスケープ: sed -e 's/ /\\ /g'

これがこのワンライナーの肝だと思います。
何をしているかと言うと、半角スペースにバックスラッシュを付けてエスケープさせています。
理由としては、このまま rm コマンドに渡してしまうと、半角スペースで区切られて別ファイルとして扱われてしまうためです。


例: hoge 2.txt というファイルを削除する場合

  1. rm hoge 2.txt を実行してみる
  2. rm コマンドの気持ちとしては hoge2.txt というファイルが渡されたと勘違い
  3. 「そんなファイルは無いよ」と言われるか運悪く hoge または 2.txt ファイルが意図せず削除されてしまう

なんかスラッシュばっかりで分かりにくいとは思いますが、置換コマンドは

$ sed -e 's/${置換前}/${置換後}/g'

でその行に含まれた全ての対象を置換してくれます。
このコマンドとの diff を取れば、

  • 置換前テキスト: ${半角スペース}
  • 置換後テキスト: \\${半角スペース}

というように指定していることが分かると思います。
さらに、バックスラッシュが連続しているのは、バックスラッシュそのものをエスケープするためで、実質 1 つのバックスラッシュを付けています。
つまり、半角スペースの前にバックスラッシュを付けているのです。

これにより、 rm コマンドが勘違いせずやってくれるはずです。

4. 削除: xargs rm

なんてことはないですが、最後に絞り込まれ、半角スペースをエスケープしたファイル名が指定され、削除される。

という全体的な流れです。

参考

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCodeでswiftの環境設定をする方法

はじめに

VSCodeでSwiftを使うための環境設定の方法を丁寧に紹介します。
競技プログラミングでSwiftを使いたい、見慣れたVSCodeで使いたい、Xcodeで標準入力の仕方がよくわからないといった方におすすめです。

開発環境

OS: macOS Catalina Version 10.15.6
Visual Studio Code (VSCode): Version 1.49.1

手順

  1. Xcodeのインストール
  2. VSCodeのインストール
  3. NodeとNPMのインストール
  4. SourceKit-LSP Extension for Visual Studio Codeのインストールとビルド
  5. SourceKit-LSPの設定

1. Xcodeのインストール

Xcodeがすでにインストールされている場合は飛ばして構いません。
Xcodeのインストール方法: https://techacademy.jp/magazine/1409

2. VSCodeのインストール

VSCodeがすでにインストールされている場合は飛ばして構いません。
VSCodeのインストール方法: https://qiita.com/watamura/items/51c70fbb848e5f956fd6

3. NodeとNPMのインストール

Homebrewを使ってnodeをインストールします。同時にnpmもインストールされます。

$ brew install node

インストールされたことを確認するために以下のコマンドを実行してみてください。

$ npm --version                                                            
6.14.8

4. SourceKit-LSP Extension for Visual Studio Codeのインストールとビルド

コマンドラインからSourceKit-LSPをクローンします。

$ git clone https://github.com/apple/sourcekit-lsp.git

次にインストールされたフォルダへ移動します。

$ cd sourcekit-lsp/Editors/vscode/

拡張パックをビルドします。

$ npm run createDevPackage

ビルドしたものをインストールします。

$ code --install-extension out/sourcekit-lsp-vscode-dev.vsix

5. SourceKit-LSPの設定

VSCodeでSwiftのファイルを実行しようとした際に以下のエラーが出た場合には、VSCodeの設定を変更します。

Couldn't start client SourceKit Language Server

Cmd+Shift+Pでコマンドパレットを開いたあとに
基本設定: 設定(JSON)を開く (Preferences: Open Settings (JSON)"
を選択します。
そしたら、すでにあるJSONに以下の文章を追加しましょう。(書き換えではなく追加です。)

"sourcekit-lsp.serverPath": "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/sourcekit-lsp"

6. 確認

hello_world.swiftというファイルを作成しましょう。
作成したファイルに以下のコードを記入してみます。

hello_world.swift
print("Hello, world!")

ターミナルで以下のように書くと実行することができます。

swift hello_world.swift

または、Control+Option+nでも実行することができます。
ちなみに実行結果は

Hello, world!

です。

7. 型推論

今のままでは型推論がされない(と思う)ので、以下のように実行してください。ファイルをまず開きます。左下に

(HDの名前) > ユーザ > (あなたのユーザ名) > ...

と表示されていると思います。その中から、(あなたのユーザ名)をクリックします。以下順番にファイルを開いていきます。
sourcekit-lspを開きます。
Editorsを開きます。
vscodeを開きます。
outを開きます。
sourcekit-lsp-vscode-dev.vsixというファイルが有ることを確認してください。
確認できたらこのページを開きっぱなしにしておきます。
VSCodeの拡張機能タブから右上の・・・を選択し、VSIXからのインストールをクリックします。
VSIXからのインストール.png
すると、インストールするファイルを選ぶ画面が表示されるので先程開いたsourcekit-lsp-vscode-dev.vsixをドラッグ&ドロップします。
最後に、開いたファイルをインストールしましょう。
これで型推論ができるようになりました!

8.追記(C言語やC++のコンパイルにsourcekit-lspを用いたくない人へ)

lsp-sourcekitのページには以下のように、Swiftだけでなく、C、C++、Objective-Cにも対応していると書かれています。
lsp-sourcekit is a client for SourceKit-lsp, a Swift/C/C++/Objective-C language server created by Apple.
そのため、今までC言語やC++で使っていたコンパイラでは動くのにlsp-sourcekitでは動かないということが発生する可能性があります。そんなときは、Swiftを使うファイルをC言語、C++を使うファイルとは分けた上で、Swiftを使うワークスペースだけでlsp-sourcekitの拡張機能を有効にしましょう。
① SourceKit-LSPを無効にする

② 再読み込みを行う

③ 拡張機能→SourceKit-LSP→有効にする→有効にする(ワークスペース)

9. 参考文献

https://nshipster.com/vscode/
https://medium.com/swlh/ios-development-on-vscode-27be37293fe1
https://scior.hatenablog.com/entry/2019/10/27/215827

10. 終わりに

私は、まだまだ初心者なのですが参考文献に上げた3つのページを参考にVSCodeに導入してみたらうまく行ったので、共有させていただきました。
間違っているところがあったらご指摘ください。また、詳しい人がいたらぜひ記事を書いてください。私は、日本語でまとまっている記事を見つけ出すことができず、苦労しました...

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Catalinaにアップデートしたらgitコマンドでエラー。xcrun: error: invalid active developer path…

エラー内容と解決策 その1

MacをCatalinaにアップデート後、gitを使おうとしたら下記の様なエラーが出ました。

xcrun: error: invalid active developer path (/Library/Developer/CommandLineTools), missing xcrun at: /Library/Developer/CommandLineTools/usr/bin/xcrun

Command Line Toolsというものがなく怒られているらしいです。
調べたところ以下でインストール出来るとのことなので実行。

$ xcode-select --install

しかし……

またエラー

多くは前記の工程で解決するようですが、私の場合はポップアップに以下の様なエラーが出ました。

このソフトウェアは、現在ソフトウェア・アップデート・サーバから入手できないため、インストールできません。

解決策 その2

これに対する対策として、以下のページから手動でCommand Line Toolsをインストールすることで解決しました。

More Downloads for Apple Developers

appleIDでログイン後、Command Line Toolsで検索をかけ、自分のXcodeのバージョンとあったものをダウンロードして、解凍後にインストールしてすればOKです。

スクリーンショット 2020-09-21 17.00.10.png

以上!!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【入門】iOS アプリ開発 #9【ゲームの状態遷移とシーケンス動作】

はじめに

今回はゲームとしてプレイできるように、ゲームの開始からゲームオーバーなどの状態遷移や各シーケンスの動作を作成する。以下が完成イメージ。ソースコードは GitHub に公開しているので参照してほしい。

※YouTube動画
IMAGE ALT TEXT HERE

状態遷移に関する仕様書

Image100.png

スタートモードとして、ゲームを開始する時のシーケンスが詳細に定義されている。

プレイモードはゲームプレイ中の状態で、プレイヤーがミスするとそのままのエサの状態からスタートし、残りのパックマンがいなくなるとゲーム・オーバーとなる。

またプレイフィールドのエサを全て食べるとラウンド・クリアとなる。

状態遷移とシーケンス動作の設計

ゲームプレイ中での必要なシーケンスを考慮して、下記の状態遷移図を作成した。

Image41.png

スタートモードは Start, Ready, Go の3つの状態に分けてシーケンスを作成する。

プレイモードは、主に Updating, ReturnToUpdating の2つの状態からなり、
プレイヤーがミスした場合は、PlayerMiss→PlayerDisappeared→PlayerRestartの状態/シーケンスを実行し、残りのパックマンがいれば Ready状態へ、なくなれば GameOver状態となる。

またプレイフィールドのエサを全て食べると、RoundClear→PrepareFlashMaze→FlashMazeの状態/シーケンスを実行しラウンドクリアとなり、Ready状態へ移って次のラウンドが開始される。

状態遷移のソースコード

CgSceneMaze の handleSequenceメソッドに、状態遷移とシーケンス処理を実装していく。

/// Maze scene class for play mode
/// This class has some methods to draw a maze and starting messages.
class CgSceneMaze: CgSceneFrame, ActorDeligate {

    var player: CgPlayer!
    var blinky: CgGhostBlinky!
    var pinky: CgGhostPinky!
    var inky: CgGhostInky!
    var clyde: CgGhostClyde!
    var ptsManager: CgScorePtsManager!
    var specialTarget: CgSpecialTarget!
    var ghosts = CgGhostManager()
    var counter_judgeGhostsWavyChase: Int = 0

    convenience init(object: CgSceneFrame) {
        self.init(binding: object, context: object.context, sprite: object.sprite, background: object.background, sound: object.sound)
        player = CgPlayer(binding: self, deligateActor: self)
        blinky = CgGhostBlinky(binding: self, deligateActor: self)
        pinky  = CgGhostPinky(binding: self, deligateActor: self)
        inky   = CgGhostInky(binding: self, deligateActor: self)
        clyde  = CgGhostClyde(binding: self, deligateActor: self)
        ptsManager = CgScorePtsManager(binding: self, deligateActor: self)
        specialTarget = CgSpecialTarget(binding: self, deligateActor: self)

        ghosts.append(blinky)
        ghosts.append(pinky)
        ghosts.append(inky)
        ghosts.append(clyde)
    }

    /// States of game model
    enum EnGameModelState: Int {
        case Init = 0
        case Start, Ready, Go, Updating, ReturnToUpdating, RoundClear, PrepareFlashMaze, FlashMaze,
             PlayerMiss, PlayerDisappeared, PlayerRestart, GameOver
    }

    /// Handle sequence
    /// To override in a derived class.
    /// - Parameter sequence: Sequence number
    /// - Returns: If true, continue the sequence, if not, end the sequence.
    override func handleSequence(sequence: Int) -> Bool {
        guard let state: EnGameModelState = EnGameModelState(rawValue: sequence) else { return false }

        switch state {
            case .Init: sequenceInit()
            case .Start: sequenceStart()
            case .Ready: sequenceReady()
            case .Go: sequenceGo()
            case .Updating: sequenceUpdating()
            case .ReturnToUpdating: sequenceReturnToUpdating()
            case .RoundClear: sequenceRoundClear()
            case .PrepareFlashMaze: sequencePrepareFlashMaze()
            case .FlashMaze: sequenceFlashMaze()
            case .PlayerMiss: sequencePlayerMiss()
            case .PlayerDisappeared: seauencePlayerDisappeared()
            case .PlayerRestart: sequencePlayerRestart()

            default:
                // Stop and exit running sequence.
                return false
        }

        // Continue running sequence.
        return true
    }

    // ============================================================
    //  Execute sequence in each state.
    // ============================================================
    func sequenceInit() {
        drawBackground()
        goToNextSequence()
    }

    func sequenceStart() {
        context.resetGame()
        context.resetRound()
        context.numberOfFeeds = drawMazeWithSettingValuesAndAttributes()
        printBlinking1Up()
        printPlayers(appearance: false)
        printStateMessage(.PlayerOneReady)
        sound.enableOutput(true)
        sound.playSE(.Beginning)
        goToNextSequence(.Ready, after: 2240)
    }

    func sequenceReady() {
        printStateMessage(.ClearPlayerOne)
        printPlayers(appearance: true)
        player.reset()
        ghosts.reset()
        specialTarget.reset()
        ptsManager.reset()
        goToNextSequence(.Go, after: 1880)
    }

    func sequenceGo() {
        printStateMessage(.ClearReady)
        drawPowerFeed(state: .Blinking)
        player.start()
        ghosts.start()

        // Reset counter for wavy attack of ghosts
        counter_judgeGhostsWavyChase = 0
        goToNextSequence()
    }

    // 〜 以下、省略 〜
}

Start, Ready, Go それぞれの状態に対応するシーケンスを sequenceStart(), sequenceReady(), sequenceGo()メソッドで実装する。

仕様書とメソッドを対応させると以下の通り。

  • ”1UP”表示が点滅 → printBlinking1Up()
  • 巣の上に”PLAYER ONE”表示、巣の下に”READY”表示 → printStateMessage(.PlayerOneReady)
  • 設定パックマンの数だけ”●”がプレイフィールド外の左下に表示される →  printPlayers(appearance: false)
  • スタートミュージック → sound.playSE(.Beginning)
  • ”PLAYER ONE”表示が赤モンスターに代わり → printStateMessage(.ClearPlayerOne), ghosts.reset()
  • 設定パックマン(プレイフィールド外の左下)が1つ減る → printPlayers(appearance: true)
  • パックマンがスタート位置に表示される → player.reset()
  • ”READY!”表示が消えて、プレイモードに移る → printStateMessage(.ClearReady)

プレイモードの仕様書

プレイモードにおいては、モンスターの出現タイミングや波状攻撃、アカモンスターのスパートといった細かい仕様が定義されている。これらによってモンスターは単調な追いかけ動作にならず、またプレイヤーのゲーム進行に合わせて難易度が調整される仕組みになっている。

Image11.png

Image12.png

Image13.png

ラウンドに対して更にモンスター出現タイミングのレベル、波状攻撃のスピードレベル、スパートする残りエサ数が定義されているが、今回は固定のレベル「A」、スパートは「①イ」のみ実装していく。

プレイモードのソースコード

プレイモードの仕様は sequenceUpdating() に動作を実装していく。

    func sequenceUpdating() {
        // Player checks to collide ghost.
        let collisionResult = ghosts.detectCollision(playerPosition: player.position)

        switch collisionResult {
            case .None:
                // When it's no eat time, ghost goes out one by one.
                if player.timer_playerNotToEat.isEventFired()  {
                    player.timer_playerNotToEat.restart()
                    ghosts.setStateToGoOut(numberOfGhosts: 4, forcedOneGhost: true)
                }

                // Appearance Timing of Ghosts
                ghosts.setStateToGoOut(numberOfGhosts: context.getNumberOfGhostsForAppearace(), forcedOneGhost: false)

                // Wavy Attack of ghosts
                // - Do not count timer when Pac-Man has power.
                if !player.timer_playerWithPower.isCounting() {
                    counter_judgeGhostsWavyChase += SYSTEM_FRAME_TIME
                }

                // Select either Scatter or Chase mode.
                let chaseMode = context.judgeGhostsWavyChase(time: counter_judgeGhostsWavyChase)

                if chaseMode {
                    pinky.chase(playerPosition: player.position, playerDirection: player.direction.get())
                    inky.chase(playerPosition: player.position, blinkyPosition: blinky.position)
                    clyde.chase(playerPosition: player.position)

                } else {
                    pinky.setStateToScatter()
                    inky.setStateToScatter()
                    clyde.setStateToScatter()
                }

                // If Blinky becomes spurt or not.
                let blinkySpurt: Bool = context.judgeBlinkySpurt() && !ghosts.isGhostInNest()
                blinky.state.setSpurt(blinkySpurt)

                // Blinky doesn't become scatter mode when he spurts.
                if blinkySpurt || chaseMode {
                    blinky.chase(playerPosition: player.position)
                } else {
                    blinky.setStateToScatter()
                }

                // For debug
                ghosts.drawTargetPosition(show: true)

            case .PlayerEatsGhost:
                let pts = context.ghostPts
                ptsManager.start(kind: pts, position: ghosts.collisionPosition, interval: 1000) //ms
                context.ghostPts = pts.get2times()
                addScore(pts: pts.getScore())
                player.stop()
                player.clear()
                specialTarget.enabled = false
                ghosts.stopWithoutEscaping()
                sound.playSE(.EatGhost)
                sound.stopBGM()  // REMARKS: To change playBGM(.BgmEscaping) immediately.
                goToNextSequence(.ReturnToUpdating, after: 1000)

            case .PlayerMiss:
                goToNextSequence(.PlayerMiss)
        }

        playBGM()
    }

はじめにプレイヤーとモンスターの衝突判定を ghosts.detectCollision(playerPosition: player.position) で行う。

衝突がなければ(.None)、ノーイートタイムによって、ゴーストが巣から出ている処理を行う。4匹の中から必ず1匹は外に出して、ノーイートタイムをリスタートさせる。

次は、食べたエサの数でゴーストを巣から出していく。すでに指定の数が出ていたら何もしない。ミスして新たにスタートするときは、ミスバイパスシーケンスを通す。

CgContextクラスの getNumberOfGhostsForAppearace()メソッドは以下の通り。

    func getNumberOfGhostsForAppearace() -> Int {
        let numberOfGhosts: Int
        // Miss Bypass Sequence
        if playerMiss {
            // Level A
            if numberOfFeedsEatedByMiss < 7 {
                numberOfGhosts = 1
            } else if numberOfFeedsEatedByMiss < 17 {
                numberOfGhosts = 2
            } else if numberOfFeedsEatedByMiss < 32 {
                numberOfGhosts = 3
            } else {
                playerMiss = false
                numberOfGhosts = getNumberOfGhostsForAppearace()
            }
        } else {
            // Level A
            if numberOfFeedsEated < 30 {
                numberOfGhosts = 2
            } else if numberOfFeedsEated < 90 {
                numberOfGhosts = 3
            } else {
                numberOfGhosts = 4
            }
        }
        return numberOfGhosts
    }

sequenceUpdating() メソッドの処理フローに戻る。

次の波状攻撃は、パターンスタートからカウントしている時間 counter_judgeGhostsWavyChase によって Scatter と Chase を切り替える。
ただしプレイヤーが逆転している時は、カウントをストップする。

CgContextクラスの judgeGhostsWavyChase()メソッドは以下の通り。

    func judgeGhostsWavyChase(time: Int) -> Bool {
        let mode: Bool
        // Level A
        if time < 7000 || (time >= 27000 && time < 34000) ||
           (time >= 54000 && time < 59000) || (time >= 79000 && time < 84000) {
            mode = false
        } else {
            mode = true
        }
        return mode
    }

sequenceUpdating() メソッドの最後の処理フローでは、アカモンスター(Blinky)のスパート処理を行う。残りエサ数に達した時かつ全モンスターが巣から出ている時(!ghosts.isGhostInNest())にスパートする。

残りは衝突判定で、パックマンがモンスターを噛み付いた時(.PlayerEatsGhost)
、逆に捕まった時(.PlayerMiss)の処理をそれぞれ実装する。

まとめ

ようやくゲームとしてプレイができるようになってきた。現在のソースコードは約5000行。
次はラウンドによって変化する難易度レベルやスピードレベルの詳細を作り込んでいく。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docker for macでThe data couldn’t be read because it is missingのエラーが出る場合

mac 10.15.6docker 2.3.0.5

以下のエラーが出ます。

Failed to analyse

The data couldn’t be read because it is missing

以下のページからdocker 2.3.0.4にダウングレードすると起動できました。

https://docs.docker.com/docker-for-mac/release-notes/#docker-desktop-community-2304

issue : https://github.com/docker/for-mac/issues/4526

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

MacのTimeMachineのバックアップ先としてUbuntu 20.04のnetatalkを使う

背景

手元のMacを修理に出す前にバックアップを取る必要があった。
ディスクに余裕があるLinuxマシン(物理マシン)があったので、それを使うことにした。

ググって出てくる情報が古くて今のバージョンに当てはまらないものだったので、情報を残しておく。

注意点

とりあえず動いたというレベルなので、設定が適当だったりします。

環境

  • MacBookPro(macOS Catalina)
  • Ubuntu 20.4
    • netatalk 3.1.12

手順

Ubuntu側

netatalkインストール

$ sudo apt install netatalk

netatalk設定

cd /etc/netatalk/

# 念のためバックアップを取る
cp afp.conf afp.conf.bk

vim afp.conf
afp.conf
;
; Netatalk 3.x configuration file
;

[Global]
; Global server settings

; [Homes]
; basedir regex = /xxxx

; [My AFP Volume]
; path = /path/to/volume

[My Time Machine Volume]
path = /var/timemachine_bk
time machine = yes
vol size limit= 256000

pathはバックアップファイルが保存される場所を指定する。
vol size limitは、自分の環境に合わせて設定する。

自分の場合は、ディスクが256GBなので、それっぽい値にした。

netatalkサービス再起動

必要かわからないけどやっておく。

sudo systemctl restart netatalk.service

保存先ディレクトリの準備

自分の環境に合わせて設定する。

mkdir /var/timemachine_bk
chown aki:aki /var/timemachine_bk

Mac側

設定で、TimeMachineを選択する。

自動的にUbuntuに作成したボリュームが認識されているはず。

あとはTimeMachieの設定なので割愛。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む