You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

启用App Sandbox的macOS SwiftUI应用,如何用Shell命令+书签创建文件夹?

App Sandbox下使用Security-Scoped书签后,Shell命令创建文件夹失败的解决方案

问题描述

我做的应用逻辑是:用户通过NSOpenPanel选目录,用security-scoped bookmark保存访问权限,再通过Process执行mkdir命令在选中目录里创建文件夹——书签本来应该能保证应用重启后还能访问该目录。但启用App Sandbox后功能直接失效,关掉沙箱就正常。哪怕已经解析了书签、拿到了访问权限,还是会弹出「Operation not permitted」错误,明显是沙箱限制了Shell命令对目录的操作。相关代码如下:

import SwiftUI

struct ContentView: View {
    
    @State private var folderName: String = ""
    @State private var bookmarkData: Data? = nil
    @State private var errorMessage: String? = nil
    
    var body: some View {
        VStack(spacing: 20) {
            TextField("Enter folder name", text: $folderName)
                .padding()
            
            Button("Choose Desktop Folder") {
                chooseDesktopFolder()
            }
            
            Button("Create Folder") {
                createFolder()
            }
            .disabled(bookmarkData == nil || folderName.isEmpty)
            
            if let errorMessage = errorMessage {
                Text(errorMessage)
                    .foregroundColor(.red)
            }
        }
        .padding()
        .frame(width: 400, height: 200)
    }
    
    private func chooseDesktopFolder() {
        let openPanel = NSOpenPanel()
        openPanel.canChooseDirectories = true
        openPanel.canChooseFiles = false
        openPanel.allowsMultipleSelection = false
        openPanel.prompt = "Select Folder"
        
        openPanel.begin { response in
            if response == .OK, let selectedURL = openPanel.url {
                do {
                    let bookmark = try selectedURL.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)
                    self.bookmarkData = bookmark
                    self.errorMessage = "Folder selected successfully."
                } catch {
                    self.errorMessage = "Failed to create bookmark: \(error.localizedDescription)"
                }
            } else {
                self.errorMessage = "No folder selected."
            }
        }
    }
    
    private func createFolder() {
        guard let bookmarkData = bookmarkData else {
            errorMessage = "No folder selected."
            return
        }
        
        do {
            var isStale = false
            let folderURL = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
            
            guard folderURL.startAccessingSecurityScopedResource() else {
                errorMessage = "Failed to access folder."
                return
            }
            
            defer { folderURL.stopAccessingSecurityScopedResource() }
            
            let newFolderURL = folderURL.appendingPathComponent(folderName)
            
            let process = Process()
            process.executableURL = URL(fileURLWithPath: "/bin/mkdir")
            process.arguments = [newFolderURL.path]
            
            try process.run()
            process.waitUntilExit()
            
            if process.terminationStatus == 0 {
                errorMessage = "Folder created successfully."
            } else {
                errorMessage = "Failed to create folder with mkdir command."
            }
        } catch {
            errorMessage = "Error: \(error.localizedDescription)"
        }
    }
}

问题根源

security-scoped的资源访问权限只属于当前应用进程,通过Process启动的子进程(比如mkdir)根本继承不了这个权限,所以子进程没有访问目标目录的资格,自然报错。

解决方案

直接用Foundation框架的FileManagerAPI创建文件夹,不要调用Shell命令——这样就能在沙箱和权限范围内完成操作。

修改createFolder()方法里的文件夹创建逻辑,替换掉原来的Process相关代码:

private func createFolder() {
    guard let bookmarkData = bookmarkData else {
        errorMessage = "No folder selected."
        return
    }
    
    do {
        var isStale = false
        let folderURL = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
        
        guard folderURL.startAccessingSecurityScopedResource() else {
            errorMessage = "Failed to access folder."
            return
        }
        defer { folderURL.stopAccessingSecurityScopedResource() }
        
        let newFolderURL = folderURL.appendingPathComponent(folderName)
        
        // 用FileManager替代Shell命令创建文件夹
        try FileManager.default.createDirectory(at: newFolderURL, withIntermediateDirectories: true)
        errorMessage = "Folder created successfully."
        
    } catch {
        errorMessage = "Error: \(error.localizedDescription)"
    }
}

额外说明

  • 沙箱环境下,子进程无法继承主进程的security-scoped权限,所有文件操作最好都在主进程内用系统API完成。
  • FileManager.default.createDirectory本身支持沙箱权限,只要主进程已经通过startAccessingSecurityScopedResource()拿到了目录访问权,就能正常执行创建。
  • 如果确实需要调用子进程处理文件,得用XPC服务的方式,给XPC配置对应沙箱权限,但这会增加复杂度,单纯创建文件夹的话,用FileManager是最省心的方案。

内容的提问来源于stack exchange,提问作者swiftPunk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 02:35:17