MacOS钥匙串授权应用程序获得密码

文章目录

  • MacOS钥匙串授权应用程序获得密码
  • `MacOS`钥匙串授权应用程序获得密码功能
  • 复制`security`命令
  • 使用`python`获取密码
  • 使用另一个版本的`python`获取密码
  • 复制`python`
  • 再次编译`python`
  • 使用相同路径编译`python`
  • 编译成共享库
  • 打包成独立文件
  • 使用`Objective-C`获得密码
  • 使用`Swift`获得密码

MacOS钥匙串授权应用程序获得密码功能

MacOS自带钥匙串功能,可以安全地储存密码并自动输入。还可以在需要时轻松查找密码。当程序请求获得密码时,会出现如下提示框:

单击拒绝按钮将拒绝授权该应用程序获得该密码,单击允许按钮将授权该应用程序本次获得该密码,下次该应用程序尝试获得密码时,仍将出现该对话框。单击始终允许按钮也将授权该应用程序本次获得该密码,区别在于下次该应用程序尝试获得密码时不再出现该对话框。

单击始终允许后,启动钥匙串访问应用程序,在右上角的搜索框中搜索密码项的名称。

打开该密码项。

然后切换到访问控制

可以看到始终允许通过这些应用程序访问列表中出现了前面请求获得密码的程序名称security。在始终允许通过这些应用程序访问下侧有+加号和-减号。

选中始终允许通过这些应用程序访问列表中的security,单击-减号,将从始终允许通过这些应用程序访问列表中删除security项,单击存储更改按钮,并在出现的钥匙串访问对话框中输入钥匙串的密码。下次security应用程序尝试获得密码时,将再次出现授权对话框。

单击下侧的+加号,将出现文件选择对话框,在其中导航至本例的应用程序/usr/bin/security,单击确定将把security添加到始终允许通过这些应用程序访问列表中,单击存储更改按钮,并在出现的钥匙串访问对话框中输入钥匙串的密码。再次运行security程序请求获得密码时,不再出现授权提示框。

从操作流程来看,直观的印象就是始终允许通过这些应用程序访问列表的授权是应用程序的路径/usr/bin/security,接下来做一下实验。

复制security命令

先使用MacOS自带的security命令获取密码,并按照上述操作授权。

/usr/bin/security find-generic-password -l "test_key" -gw

可以看到始终允许通过这些应用程序访问列表中出现了程序名称security

然后复制security命令到任意文件夹。

cp /usr/bin/security ./securityTest

运行命令获取密码。

./securityTest find-generic-password -l "test_key" -gw

会发现可以直接获得密码,并没有出现授权提示框。

选中始终允许通过这些应用程序访问列表中的security,单击-减号,将从始终允许通过这些应用程序访问列表中删除security项,单击存储更改按钮,并在出现的钥匙串访问对话框中输入钥匙串的密码。

再次运行命令尝试获取密码时,将出现授权提示框。

单击允许按钮将授权命令本次获得该密码,再次运行命令尝试获得密码时,仍将出现该对话框。单击始终允许按钮也将授权命令本次获得该密码,再次运行命令尝试获得密码时不再出现该对话框。打开该密码项,然后切换到访问控制,可以看到始终允许通过这些应用程序访问列表中出现了前面请求获得密码的程序名称security

使用MacOS自带的security命令获取密码。

/usr/bin/security find-generic-password -l "test_key" -gw

会发现可以直接获得密码,并没有出现授权提示框。

选中始终允许通过这些应用程序访问列表中的securityTest,单击-减号,将从始终允许通过这些应用程序访问列表中删除securityTest项,单击存储更改按钮,并在出现的钥匙串访问对话框中输入钥匙串的密码。再次运行security命令尝试获取密码时,将出现授权提示框。单击下侧的+加号,将出现文件选择对话框,在其中导航至本例复制的命令securityTest,单击确定将把security添加到始终允许通过这些应用程序访问列表中,单击存储更改按钮,并在出现的钥匙串访问对话框中输入钥匙串的密码。再次运行security程序请求获得密码时,不再出现授权提示框。

从这两个例子可以看出,钥匙串授权应用程序并非是依赖应用程序的路径,而是程序自身的某种签名

使用python获取密码

接下来尝试通过编程获取密码,为了简明直观的叙述问题,选择脚本语言python。

尽管MacOS自带python,为对比起见,使用pyenv安装python,pyenv还包括命令python-build,稍后用于编译python。

首先安装pyenv。

brew install pyenv

然后使用pyenv安装指定版本的python,本例中安装版本3.8.2

pyenv install --verbose 3.8.2

安装后查询安装的路径

pyenv prefix 3.8.2

结果为

~/.pyenv/versions/3.8.2

3.8.2版本中安装用于访问钥匙串的keyring包。

~/.pyenv/versions/3.8.2/bin/pip install keyring

然后运行请求获得密码的程序。

~/.pyenv/versions/3.8.2/bin/python -c 'import keyring; print(keyring.get_password("test_key", "test_username"));'

将出现如下提示框

单击允许按钮将授权python本次获得该密码,再次运行python尝试获得密码时,仍将出现该对话框。单击始终允许按钮也将授权python本次获得该密码,再次运行python尝试获得密码时不再出现该对话框。打开该密码项,然后切换到访问控制,可以看到始终允许通过这些应用程序访问列表中出现了程序名称python3.8

使用另一个版本的python获取密码

接下来使用pyenv安装另一个版本的python,本例中安装版本3.8.1

pyenv install --verbose 3.8.1

安装后查询安装的路径

pyenv prefix 3.8.1

结果为

~/.pyenv/versions/3.8.1

3.8.1版本中安装用于访问钥匙串的keyring包。

~/.pyenv/versions/3.8.1/bin/pip install keyring

然后运行请求获得密码的程序。

~/.pyenv/versions/3.8.1/bin/python -c 'import keyring; print(keyring.get_password("test_key", "test_username"));'

出现和前次3.8.2版本相同的提示框,前面已经授权3.8.2版本的python,再次出现提示框可见这两个版本的python被视为不同程序。

单击允许按钮将授权3.8.1版本的python本次获得该密码,再次运行3.8.1版本的python尝试获得密码时,仍将出现该对话框。单击始终允许按钮也将授权3.8.1版本的python本次获得该密码,再次运行3.8.1版本的python尝试获得密码时不再出现该对话框。打开该密码项,然后切换到访问控制,可以看到始终允许通过这些应用程序访问列表中出现了两个程序名称python3.8

由此可见,尽管这两个版本的python都显示为python3.8,然而钥匙串视为不同都应用程序需要分别授权。

复制python

接下来尝试复制python。

cp -r ~/.pyenv/versions/3.8.2 ~/.pyenv/versions/3.8.2-copy

由于始终允许通过这些应用程序访问列表中显示的两个python3.8难以区分,为避免混淆,清除始终允许通过这些应用程序访问列表中已经授权的两个程序。

重新授权给3.8.2版本的python。

~/.pyenv/versions/3.8.2/bin/python -c 'import keyring; print(keyring.get_password("test_key", "test_username"));'

然后运行复制的3.8.2版本的python。

~/.pyenv/versions/3.8.2-copy/bin/python -c 'import keyring; print(keyring.get_password("test_key", "test_username"));'

没有出现授权提示框,查看密码项的始终允许通过这些应用程序访问列表会看到只有一个3.8.2,说明仍旧只授权了一次,复制的3.8.2版本的python是按照前一次的授权获得密码的。

从这个测试也可以看到是按照程序而不是路径授权的。

再次编译python

既然复制文件被视为相同,那么编译相同的版本呢?保留前面安装的3.8.1版本,再次编译一遍,直接使用安装pyenv时自带的命令python-build。

python-build 3.8.1 ~/.pyenv/versions/3.8.1-build2

在第二次编译的3.8.1版本中安装用于访问钥匙串的keyring包。

~/.pyenv/versions/3.8.1-build2/bin/pip install keyring

先运行第一次编译的3.8.1版本。

~/.pyenv/versions/3.8.1/bin/python -c 'import keyring; print(keyring.get_password("test_key", "test_username"));'

然后运行第二次编译的3.8.1版本。

~/.pyenv/versions/3.8.1-build2/bin/python -c 'import keyring; print(keyring.get_password("test_key", "test_username"));'

出现授权提示框,说明即使使用相同源代码编译,也会因为编译环境的区别导致被视为不同程序。

使用相同路径编译python

先移动第一次编译的程序

mv ~/.pyenv/versions/3.8.1 ~/.pyenv/versions/3.8.1-build1

使用移动后的程序运行。

~/.pyenv/versions/3.8.1-build1/bin/python -c 'import keyring; print(keyring.get_password("test_key", "test_username"));'

没有出现授权提示框,使用和之前相同的命令安装3.8.1版本。

pyenv install --verbose 3.8.1

在新安装的3.8.1版本中安装用于访问钥匙串的keyring包。

~/.pyenv/versions/3.8.1/bin/pip install keyring

然后在新安装的3.8.1版本中运行请求获得密码的程序。

~/.pyenv/versions/3.8.1/bin/python -c 'import keyring; print(keyring.get_password("test_key", "test_username"));'

出现授权提示框,经过二进制比较会发现,由于程序中包含编译时的临时文件夹信息,两次编译的临时文件夹路径不同,尽管目标路径相同,文件也不相同。

下面用第一次编译的可执行程序覆盖第二次编译的可执行程序。

cp ~/.pyenv/versions/3.8.1-build1/bin/python ~/.pyenv/versions/3.8.1/bin/python

然后在新安装的3.8.1版本中用第一次编译的可执行程序运行请求获得密码的程序。

~/.pyenv/versions/3.8.1/bin/python -c 'import keyring; print(keyring.get_password("test_key", "test_username"));'

没有出现授权提示框。

从这个例子可以看出,钥匙串校验的是尝试获取密码的可执行程序,而不是路径。

本例中校验的是python文件,当钥匙串授权python可以获得指定密码时,任何程序调用python都可以获得该密码。甚至并不需要调用相同位置的python程序,只需要python可执行程序相同,即可通过钥匙串的校验。如果两个程序调用相同的python可执行程序获得所需的密码,那么也可以获得另一个程序的密码。

编译成共享库

当应用程序中的APP尝试获得密码时,如果选择始终允许始终允许通过这些应用程序访问列表中出现的是APP的名称,把python编译成共享库将得到相同效果。

sudo env PYTHON_CONFIGURE_OPTS="--enable-framework" python-build 3.8.2 ~/.pyenv/versions/3.8.2-framework

在重新编译的3.8.2版本中安装用于访问钥匙串的keyring包。

sudo ~/.pyenv/versions/3.8.2-framework/bin/pip install keyring

在密码项中清空始终允许通过这些应用程序访问列表,然后在重新编译的3.8.2版本中运行获取密码的脚本。

~/.pyenv/versions/3.8.2-framework/bin/python -c 'import keyring; print(keyring.get_password("test_key", "test_username"));'

出现授权提示框,单击始终允许按钮授权后,打开该密码项,然后切换到访问控制,可以看到始终允许通过这些应用程序访问列表中出现了不同于前面的程序名称Python.app

这是因为编译时使用了PYTHON_CONFIGURE_OPTS="--enable-framework"环境变量,该环境变量使python-build编译时添加--enable-framework参数。

不包含--enable-framework参数编译的目录结构为

  • ~/.pyenv/versions/3.8.2

    • ~/.pyenv/versions/3.8.2/bin
    • ~/.pyenv/versions/3.8.2/include
    • ~/.pyenv/versions/3.8.2/lib
    • ~/.pyenv/versions/3.8.2/share

包含--enable-framework参数编译的目录结构为

  • ~/.pyenv/versions/3.8.2-framework

    • ~/.pyenv/versions/3.8.2-framework/bin -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/Current/bin -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/bin
    • ~/.pyenv/versions/3.8.2-framework/bin.orig -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/Current/bin -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/bin
    • ~/.pyenv/versions/3.8.2-framework/include -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/Current/include -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/include
    • ~/.pyenv/versions/3.8.2-framework/lib -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/Current/lib -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/lib
    • ~/.pyenv/versions/3.8.2-framework/Python.framework
      • ~/.pyenv/versions/3.8.2-framework/Python.framework/Headers -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/Current/include/python3.8 -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/include/python3.8
      • ~/.pyenv/versions/3.8.2-framework/Python.framework/Python -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/Current/Python -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Python
      • ~/.pyenv/versions/3.8.2-framework/Python.framework/Resources -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/Current/Resources -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources
      • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions
        • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8

          • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/bin
          • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Headers -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/include/python3.8
          • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/include
            • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/include/python3.8
          • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/lib
          • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Python
          • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources
            • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources/English.lproj
            • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources/Info.plist
            • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources/Python.app
          • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/share
        • ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/Current -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8
    • ~/.pyenv/versions/3.8.2-framework/share -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/Current/share -> ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/share

密码项的始终允许通过这些应用程序访问列表中出现的程序名称Python.app,即为编译中的~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources/Python.app

始终允许通过这些应用程序访问列表中删除Python.app,然后运行脚本,会看到授权提示框。单击始终允许通过这些应用程序访问列表下侧的+加号,添加~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources/Python.app,再次运行脚本,不出现授权提示框。由此可见,始终允许通过这些应用程序访问列表不仅支持可执行文件,也支持APP程序包。

打开程序包~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources/Python.app可以看到里面包含的文件,直接执行其中的可执行文件。

~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources/Python.app/Contents/MacOS/Python -c 'import keyring; print(keyring.get_password("test_key", "test_username"));'

可以获得密码而不出现授权提示框,说明程序包中的可执行文件也是使用前面对~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources/Python.app的授权。

复制程序包。

sudo cp -r ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources/Python.app ~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources/Python-copy.app

然后运行复制的程序包中的可执行程序。

~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources/Python-copy.app/Contents/MacOS/Python -c 'import keyring; print(keyring.get_password("test_key", "test_username"));'

可以获得密码而不出现授权提示框,说明复制的程序包中的可执行文件也是使用前面对~/.pyenv/versions/3.8.2-framework/Python.framework/Versions/3.8/Resources/Python.app的授权。

打包成独立文件

从前面的共享库案例可以看出,授权给Python.app后,可以任意复制已经获得授权的Python.app。结合前面几个案例,当用户在一个场景中授权一个可执行程序或者APP获取密码后,如果另一个场景中也调用相同可执行程序或者APP尝试获取相同的密码,不会出现授权提示框。

既然像python可执行程序并不能限制授权后仅获得指定的密码,因此,应当避免使用类似于Python.app这样的公共APP获取密码,把获取密码的脚本打包成独立的文件即可在一定程度上避免这种情况。

接下来使用pyinstaller打包成独立文件,在重新编译的3.8.2版本中安装用于访问钥匙串的pyinstaller包。

sudo ~/.pyenv/versions/3.8.2-framework/bin/pip install pyinstaller

创建文件写入前面的脚本。

code get_password.py

文件内容为:

import keyring;
import keyring.backends.OS_X;
keyring.set_keyring(keyring.backends.OS_X.Keyring());
print(keyring.get_password("test_key", "test_username"));

使用pyinstaller打包成独立文件:

~/.pyenv/versions/3.8.2-framework/bin/pyinstaller --onefile ./get_password.py

打包后运行

./dist/get_password

会出现如下提示框:

从提示框中可以看出,请求授权的程序是打包成的独立文件get_password,而不是打包进的python,选择始终允许,打开密码项,可以看到始终允许通过这些应用程序访问列表中出现的也是打包成的独立文件get_password的名称,

打包中如果遇到异常,可以使用如下命令清除临时文件。

trash ./__pycache__ ./build ./dist ./get_password.spec

使用Objective-C获得密码

尽管使用pyinstaller打包成独立文件能在一定程度上避免前述的问题,不过打包的体积较大,而且运行速度也较慢。此时可以直接使用Objective-C获得密码。

//
//  main.m
//  FindGenericPassword
//
//  Created by 胡争辉 on 2020/5/26.
//  Copyright © 2020 胡争辉. All rights reserved.
//#import <Foundation/Foundation.h>int main(int argc, const char * argv[]) {@autoreleasepool {NSString* service = @"test_key";NSString* account = @"test_username";UInt32 pwLength = 0;void* pwData = NULL;SecKeychainItemRef itemRef = NULL;OSStatus status = SecKeychainFindGenericPassword(NULL,(UInt32) service.length,[service UTF8String],(UInt32) account.length,[account UTF8String],&pwLength,&pwData,&itemRef);if (status == errSecSuccess) {NSData* data = [NSData dataWithBytes:pwData length:pwLength];NSString* password = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];printf("%s\n", [password UTF8String]);}if (pwData) SecKeychainItemFreeContent(NULL, pwData);}return 0;
}

使用Swift获得密码

Swift代码如下

//
//  main.swift
//  FindGenericPasswordSwift
//
//  Created by 胡争辉 on 2020/5/26.
//  Copyright © 2020 胡争辉. All rights reserved.
//import Foundationvar service:String = "test_key"
var account:String = "test_username"
var pwLength:UInt32 = 0;
var pwData:UnsafeMutableRawPointer? = nil;
var item:SecKeychainItem? = nil;var status:OSStatus = SecKeychainFindGenericPassword(nil,UInt32(service.lengthOfBytes(using: String.Encoding.utf8)),service.cString(using: String.Encoding.utf8),UInt32(account.lengthOfBytes(using: String.Encoding.utf8)),account.cString(using: String.Encoding.utf8),&pwLength,&pwData,&item)
if (status == errSecSuccess) {if let myData = pwData {let password:String? = String.init(bytesNoCopy: myData, length: Int(pwLength), encoding: String.Encoding.utf8, freeWhenDone: true)if let myPassword = password {print(myPassword)}}
}

代码简单调用API实现。

运行程序会出现如下提示框:

从提示框中可以看出,请求授权的程序是打包成的独立文件FindGenericPasswordSwift,选择始终允许,打开密码项,可以看到始终允许通过这些应用程序访问列表中出现的也是打包成的独立文件FindGenericPasswordSwift的名称,

默认设置情况下,该可执行程序位于类似于如下的路径

~/Library/Developer/Xcode/DerivedData/FindGenericPasswordSwift-ddztoauqdiyyrpgystayydxtibil/Build/Products/Debug/FindGenericPasswordSwift

单独删除可执行文件,重新编译运行,没有出现提示框,说明编译出来的程序相同。

选择XCodeProduct菜单,Clean Build Folder菜单项清理构建文件夹,再次编译运行,将再次出现提示框,选择始终允许,打开密码项,可以看到始终允许通过这些应用程序访问列表中出现了两个打包成的独立文件FindGenericPasswordSwift的名称,

可执行文件的路径仍旧相同,说明重新编译的可执行文件不同,所以需要重新授权。

MacOS钥匙串授权应用程序获得密码(命令行/Python/Objective-C/Swift)相关推荐

  1. Windows API一日一练(一)第一个应用程序 使用应用程序句柄 使用命令行参数 MessageBox函数 RegisterClass和RegisterClassEx函数

    要跟计算机进行交互,就需要计算机显示信息给人看到,或者发出声音给人听到,然后人看到或听到相应的信息后,再输入其它信息给计算机,这样就可以让计算机进行数据处理,把结果显示给我们.现在就来编写一个最简单的 ...

  2. Java黑皮书课后题第7章:*7.22(计算一个字符串中大写字母的数目)编写程序,从命令行输入一个字符串,然后显示字符串中大写字母的数目

    7.22(计算一个字符串中大写字母的数目)编写程序,从命令行输入一个字符串,然后显示字符串中大写字母的数目 题目 题目描述 破题 代码 运行实例 题目 题目描述 7.22(计算一个字符串中大写字母的数 ...

  3. Java黑皮书课后题第7章:*7.21(整数求和)编写程序,从命令行输入不定数目的整数,然后显示它们的和

    *7.21(整数求和)编写程序,从命令行输入不定数目的整数,然后显示它们的和 题目 题目描述 破题 代码 运行实例 题目 题目描述 7.21(整数求和)编写程序,从命令行输入不定数目的整数,然后显示它 ...

  4. java 终端窗口是什么,java程序如何打开命令行窗口?java程序怎么运行?

    我们在编写Java程序以后都会在集成开发环境中运行程序,那么java程序如何打开命令行窗口?接下来,我们就来给大家讲解一下java程序打开命令行窗口的方法. 首先我们在命令行运行Java程序需要借助j ...

  5. 12.15有一种数叫回文数,正读和反读都一样,如12321便是一个回文数。编写一个程序,从命令行得到一个整数,判断该数是不是回文数

    有一种数叫回文数,正读和反读都一样,如12321便是一个回文数.编写一个程序,从命令行得到一个整数,判断该数是不是回文数 package Text6;import java.util.Scanner; ...

  6. 有一种数叫回文数,正读和反读都一样,如12321便是一个回文数。编写一个程序,从命令行得到一个整数,判断该数是不是回文数。

    package task08;import java.util.Scanner;/** 8.有一种数叫回文数,正读和反读都一样,如12321便是一个回文数.* 编写一个程序,从命令行得到一个整数,判断 ...

  7. 编写应用程序,从命令行传入两个整型数作为除数和被除数。要求程序中捕获NumberFormatException 异常和ArithmeticException异常,而且无论在哪种情况下,“总是被执行

    package com.bw.tryCatch;import java.util.Scanner;public class zuoYe1 {// 编写应用程序,从命令行传入两个整型数作为除数和被除数. ...

  8. python调用php命令行,python调用php函数 python怎样调用php文件中的函数详解

    前言 python调用php代码实现思路:php文件可通过在terminal中使用php命令行进行调用,因此可使用python开启子进程执行命令行代码.函数所需的参数可通过命令行传递. 测试环境 1. ...

  9. toybox执行linux程序,VirtualBox 的命令行用法

    作为一款功能强大的开源虚拟机软件,VirtualBox 不仅提供有图形化的用户界面,而且也包含命令行界面.VirtualBox 的命令行界面程序为 VBoxManage,通过它你可以完成从命令行创建虚 ...

最新文章

  1. 逗比讲算法:什么是冒泡排序?
  2. 通向未来:物联网+人工智能将成为人类的进化方向
  3. [LeetCode]*105.Construct Binary Tree from Preorder and Inorder Traversal
  4. 云计算之路-道歉,无地自容的:4月7日14:15~18:35网站故障给大家带来麻烦了
  5. 数组按逆向求最大差值的算法
  6. 是否显示展开_Creo7.0教程之绝对精度对钣金件展开的作用详解
  7. 状态机设计的一般步骤_浅谈状态机
  8. html 按钮光束,图文详解,原来3dmax光束特效的制作这么简单!
  9. BIO ,NIO,AIO的区别
  10. [转]Spring Cloud在国内中小型公司能用起来吗?
  11. 【面试虐菜】—— MongoDB知识整理
  12. 如何使用计算机改进生产线,计算机系统结构复习资料
  13. 1000以内完数c语言程序_求1000以内完数 c语言 。。
  14. 全减器的原理与vhdl实现
  15. ie浏览器出现代理服务器没有响应
  16. mysql 1556_mysqldump: Got error: 1556: You can't use locks with log tables.解决办法
  17. 字节、快手、天弘基金等 :量化/算法工程师岗位【社招|校招|实习生】
  18. Angular 2 升级到 Angular 5
  19. python计算年龄的方法_巧算年龄-随心随性无为而为-51CTO博客
  20. 【Java】自建IOS应用(IPA)发布服务器

热门文章

  1. Access-Control-Expose-Headers
  2. pytorch环境安装集锦
  3. 一种概率抽签问题的解法
  4. 野三坡龙门天关部分图片
  5. 水星mw310r虚拟服务器,水星MW310RE扩展成功后如何查看IP地址?
  6. 看知名企业CIO如何吐槽ERP
  7. USBASP USB Programmer
  8. centos7使用tar -zxvf 解压失败
  9. vue轮播图实现点击图片跳转到外部链接
  10. 利用mathematica制作二维码