├── .travis.yml ├── README.md ├── LICENSE ├── .linuxify ├── .gitignore └── linuxify /.travis.yml: -------------------------------------------------------------------------------- 1 | os: osx 2 | language: bash 3 | script: 4 | - bash -n linuxify 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # linuxify 2 | 3 | Transparently transform the macOS CLI into a fresh GNU/Linux CLI experience by 4 | 5 | - installing missing GNU programs 6 | - updating outdated GNU programs 7 | - replacing pre-installed BSD programs with their preferred GNU implementation 8 | - installing other programs common among popular GNU/Linux distributions 9 | 10 | Tested through macOS Big Sur (11), Monterey (12), Ventura (13), Sonoma (14), and Sequoia (15). 11 | 12 | ## Install 13 | 14 | ```bash 15 | git clone https://github.com/pkill37/linuxify.git 16 | cd linuxify/ 17 | ./linuxify install 18 | ``` 19 | 20 | ## Uninstall 21 | 22 | ```bash 23 | ./linuxify uninstall 24 | ``` 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Fábio Maia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.linuxify: -------------------------------------------------------------------------------- 1 | BREW_HOME=$(brew --prefix) 2 | 3 | # most programs 4 | export PATH="${BREW_HOME}/bin:$PATH" 5 | export MANPATH="${BREW_HOME}/share/man:$MANPATH" 6 | export INFOPATH="${BREW_HOME}/share/info:$INFOPATH" 7 | 8 | # coreutils 9 | export PATH="${BREW_HOME}/opt/coreutils/libexec/gnubin:$PATH" 10 | export MANPATH="${BREW_HOME}/opt/coreutils/libexec/gnuman:$MANPATH" 11 | 12 | # make 13 | export PATH="${BREW_HOME}/opt/make/libexec/gnubin:$PATH" 14 | export MANPATH="${BREW_HOME}/opt/make/libexec/gnuman:$MANPATH" 15 | 16 | # m4 17 | export PATH="${BREW_HOME}/opt/m4/bin:$PATH" 18 | 19 | # file-formula 20 | export PATH="${BREW_HOME}/opt/file-formula/bin:$PATH" 21 | 22 | # unzip 23 | export PATH="${BREW_HOME}/opt/unzip/bin:$PATH" 24 | 25 | # python 26 | export PATH="${BREW_HOME}/opt/python/libexec/bin:$PATH" 27 | 28 | # flex 29 | export PATH="${BREW_HOME}/opt/flex/bin:$PATH" 30 | export LDFLAGS="-L${BREW_HOME}/opt/flex/lib" 31 | export CPPFLAGS="-I${BREW_HOME}/opt/flex/include" 32 | 33 | # bison 34 | export PATH="${BREW_HOME}/opt/bison/bin:$PATH" 35 | export LDFLAGS="-L${BREW_HOME}/opt/bison/lib" 36 | 37 | # libressl 38 | export PATH="${BREW_HOME}/opt/libressl/bin:$PATH" 39 | export LDFLAGS="-L${BREW_HOME}/opt/libressl/lib" 40 | export CPPFLAGS="-I${BREW_HOME}/opt/libressl/include" 41 | export PKG_CONFIG_PATH="${BREW_HOME}/opt/libressl/lib/pkgconfig" 42 | 43 | # ed 44 | export PATH="${BREW_HOME}/opt/ed/libexec/gnubin:$PATH" 45 | 46 | # findutils 47 | export PATH="${BREW_HOME}/opt/findutils/libexec/gnubin:$PATH" 48 | 49 | # gnu-indent 50 | export PATH="${BREW_HOME}/opt/gnu-indent/libexec/gnubin:$PATH" 51 | 52 | # gnu-sed 53 | export PATH="${BREW_HOME}/opt/gnu-sed/libexec/gnubin:$PATH" 54 | 55 | # gnu-tar 56 | export PATH="${BREW_HOME}/opt/gnu-tar/libexec/gnubin:$PATH" 57 | 58 | # gnu-which 59 | export PATH="${BREW_HOME}/opt/gnu-which/libexec/gnubin:$PATH" 60 | 61 | # grep 62 | export PATH="${BREW_HOME}/opt/grep/libexec/gnubin:$PATH" 63 | 64 | unset BREW_HOME 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/windows,linux,osx,vim,emacs,webstorm,sublimetext,visualstudiocode 2 | 3 | ### Emacs ### 4 | # -*- mode: gitignore; -*- 5 | *~ 6 | \#*\# 7 | /.emacs.desktop 8 | /.emacs.desktop.lock 9 | *.elc 10 | auto-save-list 11 | tramp 12 | .\#* 13 | 14 | # Org-mode 15 | .org-id-locations 16 | *_archive 17 | 18 | # flymake-mode 19 | *_flymake.* 20 | 21 | # eshell files 22 | /eshell/history 23 | /eshell/lastdir 24 | 25 | # elpa packages 26 | /elpa/ 27 | 28 | # reftex files 29 | *.rel 30 | 31 | # AUCTeX auto folder 32 | /auto/ 33 | 34 | # cask packages 35 | .cask/ 36 | dist/ 37 | 38 | # Flycheck 39 | flycheck_*.el 40 | 41 | # server auth directory 42 | /server/ 43 | 44 | # projectiles files 45 | .projectile 46 | 47 | # directory configuration 48 | .dir-locals.el 49 | 50 | ### Linux ### 51 | 52 | # temporary files which can be created if a process still has a handle open of a deleted file 53 | .fuse_hidden* 54 | 55 | # KDE directory preferences 56 | .directory 57 | 58 | # Linux trash folder which might appear on any partition or disk 59 | .Trash-* 60 | 61 | # .nfs files are created when an open file is removed but is still being accessed 62 | .nfs* 63 | 64 | ### OSX ### 65 | # General 66 | .DS_Store 67 | .AppleDouble 68 | .LSOverride 69 | 70 | # Icon must end with two \r 71 | Icon 72 | 73 | # Thumbnails 74 | ._* 75 | 76 | # Files that might appear in the root of a volume 77 | .DocumentRevisions-V100 78 | .fseventsd 79 | .Spotlight-V100 80 | .TemporaryItems 81 | .Trashes 82 | .VolumeIcon.icns 83 | .com.apple.timemachine.donotpresent 84 | 85 | # Directories potentially created on remote AFP share 86 | .AppleDB 87 | .AppleDesktop 88 | Network Trash Folder 89 | Temporary Items 90 | .apdisk 91 | 92 | ### SublimeText ### 93 | # Cache files for Sublime Text 94 | *.tmlanguage.cache 95 | *.tmPreferences.cache 96 | *.stTheme.cache 97 | 98 | # Workspace files are user-specific 99 | *.sublime-workspace 100 | 101 | # Project files should be checked into the repository, unless a significant 102 | # proportion of contributors will probably not be using Sublime Text 103 | # *.sublime-project 104 | 105 | # SFTP configuration file 106 | sftp-config.json 107 | 108 | # Package control specific files 109 | Package Control.last-run 110 | Package Control.ca-list 111 | Package Control.ca-bundle 112 | Package Control.system-ca-bundle 113 | Package Control.cache/ 114 | Package Control.ca-certs/ 115 | Package Control.merged-ca-bundle 116 | Package Control.user-ca-bundle 117 | oscrypto-ca-bundle.crt 118 | bh_unicode_properties.cache 119 | 120 | # Sublime-github package stores a github token in this file 121 | # https://packagecontrol.io/packages/sublime-github 122 | GitHub.sublime-settings 123 | 124 | ### Vim ### 125 | # Swap 126 | [._]*.s[a-v][a-z] 127 | [._]*.sw[a-p] 128 | [._]s[a-rt-v][a-z] 129 | [._]ss[a-gi-z] 130 | [._]sw[a-p] 131 | 132 | # Session 133 | Session.vim 134 | 135 | # Temporary 136 | .netrwhist 137 | # Auto-generated tag files 138 | tags 139 | # Persistent undo 140 | [._]*.un~ 141 | 142 | ### VisualStudioCode ### 143 | .vscode/* 144 | !.vscode/settings.json 145 | !.vscode/tasks.json 146 | !.vscode/launch.json 147 | !.vscode/extensions.json 148 | 149 | ### WebStorm ### 150 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm 151 | # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 152 | 153 | # User-specific stuff 154 | .idea/**/workspace.xml 155 | .idea/**/tasks.xml 156 | .idea/**/usage.statistics.xml 157 | .idea/**/dictionaries 158 | .idea/**/shelf 159 | 160 | # Generated files 161 | .idea/**/contentModel.xml 162 | 163 | # Sensitive or high-churn files 164 | .idea/**/dataSources/ 165 | .idea/**/dataSources.ids 166 | .idea/**/dataSources.local.xml 167 | .idea/**/sqlDataSources.xml 168 | .idea/**/dynamic.xml 169 | .idea/**/uiDesigner.xml 170 | .idea/**/dbnavigator.xml 171 | 172 | # Gradle 173 | .idea/**/gradle.xml 174 | .idea/**/libraries 175 | 176 | # Gradle and Maven with auto-import 177 | # When using Gradle or Maven with auto-import, you should exclude module files, 178 | # since they will be recreated, and may cause churn. Uncomment if using 179 | # auto-import. 180 | # .idea/modules.xml 181 | # .idea/*.iml 182 | # .idea/modules 183 | 184 | # CMake 185 | cmake-build-*/ 186 | 187 | # Mongo Explorer plugin 188 | .idea/**/mongoSettings.xml 189 | 190 | # File-based project format 191 | *.iws 192 | 193 | # IntelliJ 194 | out/ 195 | 196 | # mpeltonen/sbt-idea plugin 197 | .idea_modules/ 198 | 199 | # JIRA plugin 200 | atlassian-ide-plugin.xml 201 | 202 | # Cursive Clojure plugin 203 | .idea/replstate.xml 204 | 205 | # Crashlytics plugin (for Android Studio and IntelliJ) 206 | com_crashlytics_export_strings.xml 207 | crashlytics.properties 208 | crashlytics-build.properties 209 | fabric.properties 210 | 211 | # Editor-based Rest Client 212 | .idea/httpRequests 213 | 214 | ### WebStorm Patch ### 215 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 216 | 217 | # *.iml 218 | # modules.xml 219 | # .idea/misc.xml 220 | # *.ipr 221 | 222 | # Sonarlint plugin 223 | .idea/sonarlint 224 | 225 | ### Windows ### 226 | # Windows thumbnail cache files 227 | Thumbs.db 228 | ehthumbs.db 229 | ehthumbs_vista.db 230 | 231 | # Dump file 232 | *.stackdump 233 | 234 | # Folder config file 235 | [Dd]esktop.ini 236 | 237 | # Recycle Bin used on file shares 238 | $RECYCLE.BIN/ 239 | 240 | # Windows Installer files 241 | *.cab 242 | *.msi 243 | *.msix 244 | *.msm 245 | *.msp 246 | 247 | # Windows shortcuts 248 | *.lnk 249 | 250 | 251 | # End of https://www.gitignore.io/api/windows,linux,osx,vim,emacs,webstorm,sublimetext,visualstudiocode, 252 | -------------------------------------------------------------------------------- /linuxify: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -euo pipefail 3 | 4 | # Get full CPU brand string (e.g., "Apple M1" or "Intel(R) Core(TM) i5-...") 5 | # This is used to detect Apple Silicon vs Intel Macs 6 | export CPU_BRAND_STRING="$(sysctl -n machdep.cpu.brand_string 2>/dev/null || echo 'Unknown')" 7 | 8 | linuxify_check_os() { 9 | if ! [[ "$OSTYPE" =~ darwin* ]]; then 10 | echo "This is meant to be run on macOS only" 11 | exit 12 | fi 13 | } 14 | 15 | linuxify_check_brew() { 16 | if ! command -v brew > /dev/null; then 17 | echo "Homebrew not installed!" 18 | echo "In order to use this script please install homebrew from https://brew.sh" 19 | exit 20 | fi 21 | } 22 | 23 | linuxify_set_prefix() { 24 | export BREW_PREFIX="$(brew --prefix)" 25 | } 26 | 27 | linuxify_check_dirs() { 28 | result=0 29 | for dir in ${BREW_PREFIX}/bin ${BREW_PREFIX}/sbin; do 30 | if [[ ! -d $dir || ! -w $dir ]]; then 31 | echo "$dir must exist and be writeable" 32 | result=1 33 | fi 34 | done 35 | 36 | return $result 37 | } 38 | 39 | linuxify_formulas=( 40 | # GNU programs non-existing in macOS 41 | "watch" 42 | "wget" 43 | "wdiff" 44 | # "gdb" intel only 45 | "autoconf" 46 | 47 | # GNU programs whose BSD counterpart is installed in macOS 48 | "coreutils" 49 | "binutils" 50 | "diffutils" 51 | "ed" 52 | "findutils" 53 | "gawk" 54 | "gnu-indent" 55 | "gnu-sed" 56 | "gnu-tar" 57 | "gnu-which" 58 | "grep" 59 | "gzip" 60 | "screen" 61 | 62 | # GNU programs existing in macOS which are outdated 63 | "bash" 64 | "emacs" 65 | "gpatch" 66 | "less" 67 | "m4" 68 | "make" 69 | "nano" 70 | "bison" 71 | 72 | # BSD programs existing in macOS which are outdated 73 | "flex" 74 | 75 | # Other common/preferred programs in GNU/Linux distributions 76 | "libressl" 77 | "file-formula" 78 | "git" 79 | "openssh" 80 | "perl" 81 | "python" 82 | "rsync" 83 | "unzip" 84 | "vim" 85 | ) 86 | 87 | linuxify_install_gdb() { 88 | if ! brew ls --versions gdb > /dev/null; then 89 | echo "Installing gdb" 90 | brew install gdb 91 | fi 92 | # gdb requires special privileges to access Mach ports. 93 | # One can either codesign the binary as per https://sourceware.org/gdb/wiki/BuildingOnDarwin 94 | # Or, on 10.12 Sierra or later with SIP, declare `set startup-with-shell off` in `~/.gdbinit`: 95 | grep -qF 'set startup-with-shell off' ~/.gdbinit || echo 'set startup-with-shell off' | tee -a ~/.gdbinit > /dev/null 96 | } 97 | 98 | linuxify_install() { 99 | linuxify_check_os; 100 | linuxify_check_brew; 101 | linuxify_set_prefix; 102 | linuxify_check_dirs; 103 | 104 | # Install all formulas 105 | for (( i=0; i<${#linuxify_formulas[@]}; i++ )); do 106 | if brew ls --versions ${linuxify_formulas[i]}; then 107 | echo "Found Existing ${linuxify_formulas[i]}" 108 | else 109 | echo "Installing ${linuxify_formulas[i]}" 110 | brew install ${linuxify_formulas[i]} 111 | fi 112 | done 113 | 114 | # Only install gdb on Intel Macs (not supported on Apple Silicon) 115 | if [[ ! $CPU_BRAND_STRING =~ ^Apple ]]; then 116 | linuxify_install_gdb 117 | fi 118 | 119 | # Offer to change shell to newly installed bash 120 | read -p "Do you want to change your shell to the latest bash (Y/N)? " -n 1 -r 121 | if [[ $REPLY =~ [Yy]$ ]]; then 122 | grep -qF "${BREW_PREFIX}/bin/bash" /etc/shells || echo "${BREW_PREFIX}/bin/bash" | sudo tee -a /etc/shells > /dev/null 123 | echo # Blank line so the password entry isn't bunched-up 124 | chsh -s ${BREW_PREFIX}/bin/bash 125 | else 126 | echo "OK, leaving your shell as $SHELL" 127 | fi 128 | 129 | 130 | # Make changes to PATH/MANPATH/INFOPATH/LDFLAGS/CPPFLAGS 131 | cp .linuxify ~/.linuxify 132 | echo "Add '. ~/.linuxify' to your ~/.bashrc, ~/.zshrc or your shell's equivalent config file" 133 | } 134 | 135 | linuxify_uninstall() { 136 | linuxify_check_os; 137 | linuxify_check_brew; 138 | linuxify_set_prefix; 139 | 140 | # Remove gdb fix 141 | [ -f ~/.gdbinit ] && sed -i.bak '/set startup-with-shell off/d' ~/.gdbinit && rm ~/.gdbinit.bak 142 | 143 | # Offer to change default shell back to macOS default 144 | # macOS 10.15 Catalina and later (including 11+ Big Sur, Monterey, Ventura, Sonoma, Sequoia): zsh 145 | # macOS 10.14 Mojave and earlier: bash 146 | bash_is_local=false 147 | if [[ $SHELL =~ local ]]; then 148 | read -p "Do you want to change your shell back to macOS default (macOS >= 10.15 Catalina ? zsh : bash) ? " -n 1 -r 149 | if [[ $REPLY =~ [Yy]$ ]]; then 150 | sudo sed -i.bak "|${BREW_PREFIX}/bin/bash|d" /etc/shells && sudo rm /etc/shells.bak 151 | echo 152 | # Get macOS version - handle both 10.x and 11+ versioning schemes 153 | macos_version=$(sw_vers -productVersion) 154 | major_version=$(echo "$macos_version" | awk -F. '{print $1}') 155 | minor_version=$(echo "$macos_version" | awk -F. '{print $2}') 156 | 157 | # macOS 10.15+ (Catalina) or macOS 11+ use zsh as default 158 | if [[ $major_version -gt 10 ]] || [[ $major_version -eq 10 && $minor_version -gt 14 ]]; then 159 | chsh -s /bin/zsh 160 | else 161 | chsh -s /bin/bash 162 | fi 163 | else 164 | echo "OK, leaving your shell as $SHELL" 165 | if [[ $SHELL == ${BREW_PREFIX}/bin/bash ]]; then 166 | bash_is_local=true 167 | fi 168 | fi 169 | fi 170 | 171 | # Uninstall all formulas in reverse order 172 | for (( i=${#linuxify_formulas[@]}-1; i>=0; i-- )); do 173 | if [[ ${linuxify_formulas[i]} != bash ]]; then 174 | brew uninstall -f $(echo "${linuxify_formulas[i]}" | cut -d ' ' -f1) 175 | fi 176 | done 177 | 178 | # Only remove bash if the user didn't elect to keep it as their shell 179 | if [[ "$bash_is_local" != true ]]; then 180 | brew uninstall bash 181 | fi 182 | 183 | # Remove changes to PATH/MANPATH/INFOPATH/LDFLAGS/CPPFLAGS 184 | rm -f ~/.linuxify 185 | echo "Remove '. ~/.linuxify' from your ~/.bashrc, ~/.zshrc or your shell's equivalent config file" 186 | } 187 | 188 | linuxify_info() { 189 | linuxify_check_os; 190 | linuxify_check_brew; 191 | 192 | for (( i=0; i<${#linuxify_formulas[@]}; i++ )); do 193 | brew info $(echo "${linuxify_formulas[i]}" | cut -d ' ' -f1) 194 | printf "\n\n===============================================================================================================================\n\n" 195 | done 196 | } 197 | 198 | linuxify_help() { 199 | echo "usage: linuxify [-h] [command]"; 200 | echo "" 201 | echo "valid commands:" 202 | echo " install install GNU/Linux utilities" 203 | echo " uninstall uninstall GNU/Linux utilities" 204 | echo " info show info on GNU/Linux utilities" 205 | echo "" 206 | echo "optional arguments:" 207 | echo " -h, --help show this help message and exit" 208 | } 209 | 210 | linuxify_main() { 211 | if [ $# -eq 1 ]; then 212 | case $1 in 213 | "install") linuxify_install ;; 214 | "uninstall") linuxify_uninstall ;; 215 | "info") linuxify_info ;; 216 | "-h") linuxify_help ;; 217 | "--help") linuxify_help ;; 218 | esac 219 | else 220 | linuxify_help; 221 | exit 222 | fi 223 | } 224 | 225 | linuxify_main "$@" 226 | --------------------------------------------------------------------------------