The wrong python interpreter is called - python

I updated my python interpreter, but I think the old one is still called. When I check for the version I get:
$ python -V
Python 3.0.1
But I believe the old interpreter is still being called. When I run the command:
python myProg.py
The script runs properly. But when I invoke it with the command
./myProg.py
I get the error message:
AttributeError: 'str' object has no attribute 'format'
Which apparently is due to the old interpreter being called. How can I fix this? I run Mac OS X 10.5. Has it something to do with the first line:
#!/usr/bin/python
I just started out with python and am not very familiar with interpreted languages, so I am not too sure what is going on.

According to the first line of the script, #!/usr/bin/python, you are calling the Python interpreter at /usr/bin/python (which is most likely the one that ships with Mac OS X). You have to change that path to the path where you installed your Python 3 interpreter (likely /usr/local/bin/python or /opt/local/bin/python); or you can just change that line to read #!/usr/bin/env python, which will call the python listed first in your PATH variable (which seems to be the newer version you installed).

Firstly, the recommended shebang line is:
#!/usr/bin/env python
This will make sure the python interpreter that is invoked when you ./foo.py is the same interpreter that is invoked when you invoke python from the command line.
From your description, I suspect that if you did:
which python
It would not give you /usr/bin/python. It would give you something else, which is where the python 3 interpreter lives. You can either modify your shebang line to the above, or replace the path to the python interpreter with the path returned by which.

Try which python. I will tell you which python interpreter is used in your environment.
If it is not /usr/bin/python like in the script, then your suspicion is confirmed.

It's very possibly what you suspect, that the shebang line is calling the older version. Two things you might want to check:
1) what version is the interpreter at /usr/bin/python:
/usr/bin/python -V
2) where is the python 3 interpreter you installed:
which python
If you get the correct one from the command line, then replace your shebang line with this:
#!/usr/bin/env python
Addendum: You could also replace the older version of python with a symlink to python 3, but beware that any major OS X updates (ie: 10.5.6 to 10.5.7) will likely break this:
sudo mv /usr/bin/python /usr/bin/python25
sudo ln -s /path/to/python/3/python /usr/bin/python

run 'which python' - if this gives a different answer than /usr/bin/python, change #!/usr/bin/python to have that path instead.

It may be a bit odd providing a Perl script to answer a Python question, but it works for Python just as well as it does for Perl. This is a script called 'fixin', meaning 'fix interpreter'. It changes the shebang line to the correct string for your current PATH.
#!/Users/jleffler/perl/v5.10.0/bin/perl
#
# #(#)$Id: fixin.pl,v 1.3 2003/03/11 21:20:08 jleffler Exp $
#
# FIXIN: from Programming Perl
# Usage: fixin [-s] [file ...]
# Configuration
$does_hashbang = 1; # Kernel recognises #!
$verbose = 1; # Verbose by default
# Construct list of directories to search.
#absdirs = reverse grep(m!^/!, split(/:/, $ENV{'PATH'}, 999));
# Process command line arguments
if ($ARGV[0] eq '-s')
{
shift;
$verbose = 0;
}
die "Usage: $0 [-s] [file ...]\n" unless #ARGV || !-t;
#ARGV = '-' unless #ARGV;
# Process each file.
FILE: foreach $filename (#ARGV)
{
open(IN, $filename) || ((warn "Can't process $filename: $!\n"), next);
$_ = <IN>;
next FILE unless /^#!/; # Not a hash/bang file
chop($cmd = $_);
$cmd =~ s/^#! *//;
($cmd, $arg) = split(' ', $cmd, 2);
$cmd =~ s!^.*/!!;
# Now look (in reverse) for interpreter in absolute path
$found = '';
foreach $dir (#absdirs)
{
if (-x "$dir/$cmd")
{
warn "Ignoring $found\n" if $verbose && $found;
$found = "$dir/$cmd";
}
}
# Figure out how to invoke interpreter on this machine
if ($found)
{
warn "Changing $filename to $found\n" if $verbose;
if ($does_hashbang)
{
$_ = "#!$found";
$_ .= ' ' . $arg if $arg ne '';
$_ .= "\n";
}
else
{
$_ = <<EOF;
:
eval 'exec $found $arg -S \$0 \${1+"\$#"}'
if \$running_under_some_shell;
EOF
}
}
else
{
warn "Can't find $cmd in PATH, $filename unchanged\n" if $verbose;
next FILE;
}
# Make new file if necessary
if ($filename eq '-') { select(STDOUT); }
else
{
rename($filename, "$filename.bak") ||
((warn "Can't modify $filename"), next FILE);
open(OUT, ">$filename") ||
die "Can't create new $filename: $!\n";
($def, $ino, $mode) = stat IN;
$mode = 0755 unless $dev;
chmod $mode, $filename;
select(OUT);
}
# Print the new #! line (or the equivalent) and copy the rest of the file.
print;
while (<IN>)
{
print;
}
close IN;
close OUT;
}
The code is derived from a script of the same name in the original Camel Book ('Programming Perl', first edition). This copy has been hacked a bit since then - and should be hacked some more. But I use it routinely -- indeed, I just copied it from one Mac to another, and since I've not installed Perl 5.10.0 on the second, I ran:
$ perl fixin fixin
Changing fixin to /usr/bin/perl
$
Thereby changing from the private install Perl to the standard one.
Exercise for the reader - rewrite the script in Python.

Related

How to run a python program from powershell based on python hashbang?

I have several python3 scripts using the hashbang for identifying it as a Python3 script. The scripts can not be identified as python based on the file extension, as they don't have any (just like in any other *nix system).
I've already looked at related issues, but they don't address this particular need, and seem to rely upon the *.py extension, for recognition.
Set up Python on Windows to not type "python" in cmd
How to make python scripts executable on Windows?
So my script is named: myscript, and the first line of the file is:
#!/usr/bin/env python3
...
How can I make Windows powershell recognize this and run it with the python interpreter located in C:\Python3.7?
UPDATE-1
To clarify, I want to run it from powershell CLI and not by clicking on it.
In addition, I just discovered (to my horror) that when you use pip install with a native Windows Python3, the first line hashbang is autotragically replaced with:
#!c:\python37\python.exe
Ouch!
UPDATE-2
Thanks to #eryksun's comments, I managed to get a PowerShell script to do some basic checking for me. However, it will need to be fixed to support more than Python.
test4script.ps1:
Param([parameter(Mandatory=$true, HelpMessage="Need a valid filename")] $fileName)
$firstLine = Get-Content -Path $fileName -TotalCount 1
$SHEBANG="^#!"
$shes=#("python3","python2","python","bash","sh","perl","pwsh")
If ($firstLine -match $SHEBANG) {
Write-Host "DEBUG: checking for common shebangs" -ForegroundColor Green
foreach ($i in $shes) {
If ($firstLine -match $i) {
Write-Host "DEBUG: found shebang for: $i" -ForegroundColor Green
C:\python37\python.exe $fileName
break
}
}
} else {
Write-Host "File is not a known script. No shebang fund in file!" -ForegroundColor Red
return
}
Write-Host "DEBUG: Done" -ForegroundColor Green
The result is:
$ Get-Content -Path nonscript -TotalCount 3
#This aint right
echo "hello"
$ Get-Content -Path pip-describe -TotalCount 3
#!c:\python37\python.exe
# pip-describe - Show full text package description from PyPI
# -*- coding: utf-8 -*-
$ .\test4script.ps1 nonscript
File is not a known script. No shebang fund in file!
$ .\test4script.ps1 pip-describe
DEBUG: checking for common shebangs
DEBUG: found shebang for: python3
Usage: pip-describe <package-name>
This will return the full-text package description (usually the README)
as found on PyPI, for any given <package-name>.
...
Now we should be able to associate this script with ., using CMD.exe with:
cmd /c assoc .=unknown
cmd /c "ftype unknown=""C:\mybin\test4script.ps1"" %1"
But it would have been nicer to do this independently with PowerShell and not having to hop through CMD.
eryksun provides great pointers in comments on the question, and your edits based on them show the way toward installing a generic, machine-wide shebang-line-aware launcher for extension-less scripts made executable by adding . to $env:PATHEXT.
Things to note about this approach:
PowerShell currently (as of PowerShell Core 6.2.0) invariably executes extension-less files in a new console window, which makes this configuration useless in PowerShell - it does work as expected from cmd.exe, though.
PowerShell's behavior should be considered a bug and was reported in this GitHub issue.
The mechanism presents a potential security risk, because any plain-text file without an extension that has a shebang line effectively becomes executable, potentially bypassing security features that focus on files that have extensions known to be executable.
Implementing a file-type definition's default operation via a [PowerShell] script invariably requires creating a child process with the script file's interpreter, which in the case at hand means invoking powershell.exe with its -File parameter. powershell.exe's startup performance cost is nontrivial, which delays execution.
If you do want to implement this generic mechanism after all, see the Install-ShebangSupport.ps1 script at the bottom.
Given the above, here's a more lightweight, Python-specific approach based on automatically creating individual *.ps1 wrapper scripts for extension-less shebang-line Python scripts:
This takes advantage of the fact that PowerShell allows execution of its own *.ps1 script files by filename only.
Limitations:
You need to run the wrapper-script-generation script (printed below) at least once, and every time you add new extension-less Python scripts.
Conceivably, a file-system watcher could be used to trigger the generation script, but setting that up is a nontrivial effort.
On the plus side, the wrapper scripts execute more quickly than a generic file-type-based solution, because no extra PowerShell instance (child process) is involved.
Run the following script from the directory in which the extension-less Python scripts are located[1]:
Get-ChildItem -File | Where-Object Extension -eq '' | % {
if ((Get-Content -LiteralPath $_.fullname -First 1) -match '^#!.*\bpython') {
#'
py.exe ($PSCommandPath -replace '\.ps1$') $Args; exit $LASTEXITCODE
'# > ($_.FullName + '.ps1')
}
}
For every extension-less Python script somescript, a companion somescript.ps1 file is created that passes somescript to the Python launcher py.exe, along with any command-line arguments; exit $LASTEXTICODE ensures that py.exe's exit code is passed through.
As eryksun notes, py.exe should be capable of interpreting the shebang line to invoke the appropriate Python executable.
If you don't want to clutter your system with wrapper files, auto-generate functions as an alternative, but note that you'll have to load them into every session to be available, typically via your $PROFILE file:
Get-ChildItem -File | Where-Object Extension -eq '' | % {
if ((Get-Content -LiteralPath $_.FullName -First 1) -match '^#!.*\bpython') {
Invoke-Expression #"
Function global:$($_.Name) {
py.exe "$($_.FullName)" `$Args
}
"#
}
}
Note:
This will make the current directory's extension-less Python scripts available as if they were located in a directory listed in $env:PATH - whether or not the current directory is listed there.
Each target Python script is hard-coded into a function of the same name and will invariably target that script.
By contrast, the *.ps1 wrapper script-file approach permits targeted invocation in a given directory, with something like .\foo.
This particular use of Invoke-Expression is safe - to define the functions based on expandable strings - but Invoke-Expression should generally be avoided.
Script Install-ShebangSupport.ps1 for installing generic support for direct execution of extension-less shebang-line-based scripts on Windows:
The script supports installation at the current-user level (by default or with -Scope CurrentUser) or at the all-users level (with -Scope AllUsers, requires running as admin).
Assuming presence in the current dir, run Get-Help .\Install-ShebangSupport for basic help.
Invoking the script without arguments prints a confirmation prompt with details about the required modifications to the system; Ctrl-C can be used to abort without installation; passing -Force performs installation without prompting for confirmation.
To uninstall later, pass -Uninstall; note that you must match the (implied) -Scope value used during installation.
Implementation note: Defining the no-extension file type via the cmd.exe-internal commands assoc and ftype invariably takes effect for all users, because the definitions are stored in the registry in HKEY_LOCAL_MACHINE\Software\Classes; also, the invocation therefore invariably requires elevation (administrative privileges).
However, it is possible to create user-level definitions by direct manipulation of the registry, which is what this script uses, also for the machine-level definitions.
Note: Syntax highlighting is broken in the code below, but it does work.
<#
.SYNOPSIS
Support for direct execution of extension-less script files with shebang lines
on Windows.
.DESCRIPTION
For details, invoke this script without arguments: the confirmation prompt
will show the required modifications to your system. Submit "N" to opt out
of the installation.
Note that extension-less files that do not have a shebang line will open in
the default text editor.
.PARAMETER Scope
Whether to install support for the current user only (the default) or
for all users (requires invocation as admin).
.PARAMETER Uninstall
Uninstalls previously installed support.
Note that the (implied) -Scope value must match the one that was used during
installation.
.PARAMETER Force
Bypasses the confirmation prompt that is shown by default.
.EXAMPLE
Install-ShebangSupport
Installation for the current user that requires answering a confirmation prompt.
.EXAMPLE
Install-ShebangSupport -Scope AllUsers -Force
Installation for all users without confirmation prompt. Requires invocation
as admin.
.EXAMPLE
Install-ShebangSupport -Uninstall
Uninstallation for the current user with confirmation prompt.
#>
[CmdletBinding(PositionalBinding=$false)]
param(
[ValidateSet('CurrentUser', 'AllUsers')]
[string] $Scope = 'CurrentUser'
,
[switch] $Force
,
[switch] $Uninstall
)
$ErrorActionPreference = 'Stop'; Set-StrictMode -Version 1
if ($env:OS -ne 'Windows_NT') { Throw ("This script can only run on Windows.")}
# ---------------------- BEGIN: Internal helper functions
# === INSTALL
function install {
Write-Verbose ('Installing shebang-script support for {0}:' -f ('the current user', 'ALL users')[$forAllUsers])
# NOTE:
# * assoc and ftype only ever operate on HKEY_LOCAL_MACHINE\Software\Classes, not HKEY_CURRENT_USER\Software\Classes - both on reading and writing.
# * *HKEY_CURRENT_USER*-level definitions DO work, but *neither assoc nor ftype report them or can update them*.
# Therefore, we perform direct registry manipulation below.
Write-Verbose 'Creating file type for extension-less file names via the registry...'
# Map the "extension-less extension", "." to the name of the new file type to be created below.
# Caveat: Sadly, New-Item -Force blindly recreates the registry key if it already exists, discarding
# all existing content in the process.
$key = New-Item -Force -Path $regkeyExtensionToFileType
$null = New-ItemProperty -LiteralPath $key.PSPath -Name '(default)' -Value $fileTypeName
# Define the new file type:
$key = New-Item -Force -Path "$regkeyFileType\Shell\Open\Command"
$null = New-ItemProperty -LiteralPath $key.PSPath -Name '(default)' -Value ('powershell.exe -noprofile -file "{0}" "%1" %*' -f $helperScriptFullName)
# Get the current $env:PATHEXT definition from the registry.
$currPathExt = [Environment]::GetEnvironmentVariable('PATHEXT', ('User', 'Machine')[$forAllUsers])
if (-not $forAllUsers -and -not $currPathExt) {
Write-Verbose "Creating a static user-level copy of the machine-level PATHEXT environment variable..."
$currPathExt = [Environment]::GetEnvironmentVariable('PATHEXT', 'Machine')
}
# Add "." as an executable extension to $env:PATHEXT so as to support
# direct execution of extension-less files.
if ($currPathExt -split ';' -notcontains '.') {
Write-Verbose "Appending '.' to PATHEXT..."
[Environment]::SetEnvironmentVariable('PATHEXT', $currPathExt + ';.', ('User', 'Machine')[$forAllUsers])
# Also define it for the running session
$env:PATHEXT += ';.'
} else {
Write-Verbose "'.' is already contained in PATHEXT."
}
# The following here-string is the source code for the
# $helperScriptFullName script to create.
# To debug and/or modify it:
# * Edit and/or debug $helperScriptFullName
# * After applying fixes / enhancements, replace the here-string
# below with the updated source code.
#'
# When invoked by direct execution of a script file via the file-type definition, the arguments are:
# * The full path of the script file being invoked.
# * Arguments passed to the script file on invocation, if any.
# CAVEAT: PowerShell's own parsing of command-line arguments into $args
# breaks unquoted tokens such as >> -true:blue << and >> -true.blue << into *2* arguments
# ('-true:', 'blue' and '-true', '.blue', respectively).
# The only way to avoid that is to pass the argument *quoted*: '-true:blue' and '-true.blue'
# See https://github.com/PowerShell/PowerShell/issues/6360
# Parse the arguments into the script
param(
[Parameter(Mandatory=$true)] [string] $LiteralPath,
[Parameter(ValueFromRemainingArguments=$true)] [array] $passThruArgs
)
$ErrorActionPreference = 'Stop'; Set-StrictMode -Version 1
# Note: When invoked via the file-type definition, $LiteralPath is guaranteed to be a full path.
# To also support interactive use of this script (for debugging), we resolve the script
# argument to a full path.
# Note that if you pass just a script filename (<script>), it'll be interpreted relative
# to the current directory rather than based on an $env:PATH search; to do the latter,
# pass (Get-Command <script>).Source
if ($LiteralPath -notmatch '^(?:[a-z]:)?[\\/]') { $LiteralPath = Convert-Path -LiteralPath $LiteralPath }
# Check the script's first line for a shebang.
$shebangLine = ''
switch -Regex -File $LiteralPath {
'^#!\s*(.*)\s*$' { # Matches a shebang line.
# Save the shebang line and its embedded command.
$shebangLine = $_
$cmdLine = $Matches[1]
Write-Verbose "Shebang line found in '$LiteralPath': $shebangLine"
break # We're done now that we have the shebang line.
}
default { # no shebang line found -> open with default text editor
# Note: We cannot use Invoke-Item or Start-Process, as that would
# reinvoke this handler, resulting in an infinite loop.
# The best we can do is to open the file in the default text editor.
Write-Verbose "No shebang line, opening with default text editor: $LiteralPath"
# Obtain the command line for the default text editor directly from the registry
# at HKEY_CLASSES_ROOT\txtfile\shell\Open\command rather than via `cmd /c ftype`,
# because assoc and ftype only ever report on and update the *machine-level* definitions at
# HKEY_LOCAL_MACHINE\Software\Classes
$cmdLine = [environment]::ExpandEnvironmentVariables((((Get-ItemProperty -EA Ignore registry::HKEY_CLASSES_ROOT\txtfile\shell\Open\command).'(default)') -split '=')[-1])
if (-not $cmdLine) { $cmdLine = 'NOTEPAD.EXE %1' } # Fall back to Notepad.
break # We're done now that we know this file doesn't have a shebang line.
}
}
# Parse the shebang line's embedded command line or the default-text-editor's command line into arguments.
# Note: We use Invoke-Expression and Write-Output so as to support *quoted*
# arguments as well - though presumably rare in practice.
# If supporting quoted tokens isn't necessary, the next line can be replaced
# with a strictly-by-whitespace splitting command:
# $cmdArgs = -split $cmdLine
[array] $cmdArgs = (Invoke-Expression "Write-Output -- $($cmdLine -replace '\$', "`0")") -replace "`0", '$'
if ($shebangLine) {
# Extract the target executable name or path.
# If the first argument is '/usr/bin/env', we skip it, as env (on Unix-like platforms) is merely used
# to locate the true target executable in the Path.
$exeTokenIndex = 0 + ($cmdArgs[0] -eq '/usr/bin/env')
$exeNameOrPath = $cmdArgs[$exeTokenIndex]
$exeFullPath = ''
# Note: We do NOT pass any remaining arguments from the shebang line through.
# (Such arguments are rare anyway.)
# The rationale is that an interpreter that understands shebang lines will
# also respect such arguments when reading the file - this is true of at
# least py.exe, the Python launcher, and ruby.exe
# Python is a special case: the Python launcher, py.exe, is itself
# capable of interpreting shebang lines, so we defer to it.
if ($exeNameOrPath -match '\bpython\d?') {
# Ensure that 'py.exe' is available; if not, we fall back to the same approach
# as for all other executables.
$exeFullPath = (Get-Command -CommandType Application py.exe -ErrorAction Ignore).Source
}
if (-not $exeFullPath) {
# Try the executable spec. as-is first, should it actually contain a *Windows* path name.
$exeFullPath = (Get-Command -CommandType Application $exeNameOrPath -ErrorAction Ignore).Source
if (-not $exeFullPath) {
# If not found, assume it is a Unix path that doesn't apply, and try to locate the hopefully
# appropriate executable by its filename only, in the Path.
$exeFullPath = (Get-Command -CommandType Application (Split-Path -Leaf -LiteralPath $exeNameOrPath) -ErrorAction Ignore).Source
}
}
# Abort, if we can't find a suitable executable.
if (-not $exeFullPath) { Throw "Could not find a suitable executable to run '$LiteralPath' based on its shebang line: $shebangLine" }
# Synthesize the complete list of arguments to pass to the target exectuable.
$passThruArgs = , $LiteralPath + $passThruArgs
} else { # NON-shebang-line files: invocation of default text editor
$exeFullPath, [array] $editorArgs = $cmdArgs -replace '%1', ($LiteralPath -replace '\$', '$$')
# Synthesize the complete list of arguments to pass to the target exectuable.
# Replace the '%1' placeholder with the script's path.
# Note that we don't really expect additional arguments to have been passed in this scenario,
# and such arguments may be interpreted as additional file arguments by the editor.
$passThruArgs = ($editorArgs -replace '"?%1"?', ($LiteralPath -replace '\$', '$$$$')) + $passThruArgs
# If the editor is a GUI application, $LASTEXITCODE won't be set by PowerShell.
# We set it to 0 here, as it has no value by default, and referencing it below with exit
# would cause an error due to Set-StrictMode -Version 1.
$LASTEXITCODE = 0
}
Write-Verbose "Executing: $exeFullPath $passThruArgs"
# Invoke the target executable with all arguments.
# Important:
# * We need to manually \-escape embeded " chars. in arguments
# because PowerShell, regrettably, doesn't do that automatically.
# However, even that may fail in edge cases in Windows PowerShell (fixed in PS Core),
# namely when an unbalanced " char. is part of the first word - see https://stackoverflow.com/a/55604316/45375
& $exeFullPath ($passThruArgs -replace '"', '\"')
# Pass the target executable's exit code through.
# (In the case of invoking the default editor for non-shebang-line files, it
# won't have been set, if the editor is a GUI application.)
exit $LASTEXITCODE
'# |
Set-Content -Encoding Utf8 -LiteralPath $helperScriptFullName
}
# === UNINSTALL
function uninstall {
Write-Verbose ('Uninstalling shebang-script support for {0}:' -f ('the current user', 'ALL users')[$forAllUsers])
Write-Verbose 'Removing file type information from the registry...'
foreach ($regKey in $regkeyExtensionToFileType, $regkeyFileType) {
if (Test-Path -LiteralPath $regKey) {
Remove-Item -Force -Recurse -LiteralPath $regkey
}
}
# Get the current $env:PATHEXT definition from the registry.
$currPathExt = [Environment]::GetEnvironmentVariable('PATHEXT', ('User', 'Machine')[$forAllUsers])
# Remove the "." entry from $env:PATHEXT
$newPathExt = ($currPathExt -split ';' -ne '.') -join ';'
if ($newPathExt -eq $currPathExt) {
Write-Verbose "'.' is not contained in PATHEXT; nothing to do."
} else {
# For user-level uninstallations: as a courtesy, we compare the new PATHEXT value
# to the machine-level one, and, if they're now the same, simply REMOVE the user-level definition.
Write-Verbose "Removing '.' from PATHEXT..."
if (-not $forAllUsers) {
$machineLevelPathExt = [Environment]::GetEnvironmentVariable('PATHEXT', 'Machine')
if ($newPathExt -eq $machineLevelPathExt) { $newPathExt = $null }
Write-Verbose "User-level PATHEXT no longer needed, removing..."
}
[Environment]::SetEnvironmentVariable('PATHEXT', $newPathExt, ('User', 'Machine')[$forAllUsers])
# Also update for the running session
$env:PATHEXT = if ($newPathExt) { $newPathExt } else { $machineLevelPathExt }
}
Write-Verbose "Removing helper PowerShell script..."
if (Test-Path -LiteralPath $helperScriptFullName) {
Remove-Item -Force -LiteralPath $helperScriptFullName
}
}
# ---------------------- END: Internal helper functions
$forAllUsers = $Scope -eq 'AllUsers'
$verb = ('install', 'uninstall')[$Uninstall.IsPresent]
$operation = $verb + 'ation'
# If -Scope AllUsers was passed, ensure that the session is elevated.
$mustElevate = $forAllUsers -and -not ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole('BUILTIN\Administrators')
if ($mustElevate) {
Throw "In order to $verb for ALL users, you must run this script WITH ELEVATED PRIVILEGES (Run As Administrator)."
}
# --- Define names, registry and file locations.
# The path of the generic shebang runner script that we'll create below.
$helperScriptFullName = Join-Path ($HOME, $env:ALLUSERSPROFILE)[$forAllUsers] 'Invoke-ShebangScript.ps1'
# The name of the file type to create for extension-less files.
$fileTypeName = 'ShebangScript'
# Registry keys that need to be modified.
# "." represents extension-less files
$regkeyExtensionToFileType = 'registry::{0}\SOFTWARE\Classes\.' -f ('HKEY_CURRENT_USER', 'HKEY_LOCAL_MACHINE')[$forAllUsers]
$regkeyFileType = 'registry::{0}\SOFTWARE\Classes\{1}' -f ('HKEY_CURRENT_USER', 'HKEY_LOCAL_MACHINE')[$forAllUsers], $fileTypeName
# ---
# Prompt for confirmation, unless -Force was passsed.
if ($Uninstall) { # UNINSTALL
if (-not $Force -and -not $PSCmdlet.ShouldContinue(#"
You are about to UNINSTALL support for direct execution of extension-less
script files that have shebang lines.
Uninstallation will be performed for $(("the CURRENT USER only`n(invoke as admin with -Scope AllUsers to change that)", 'ALL USERS')[$forAllUsers]).
IMPORTANT: Uninstallation will only be effective if it is performed in the same
(implied) -Scope as the original installation.
The following modifications to your system will be performed:
* "." will be persistently REMOVED from your `$env:PATHEXT variable.
* The following registry keys will be REMOVED:
$($regkeyExtensionToFileType -replace '^registry::')
$($regkeyFileType -replace '^registry::')
* The following helper PowerShell script will be REMOVED:
$helperScriptFullName
Press ENTER to proceed, or Ctrl-C to abort.
"#, "Shebang-Script Direct-Execution Support - Uninstallation")) { # , $true, [ref] $null, [ref] $null)) {
exit 1
}
# Call the uninstallation helper function
uninstall
} else { # INSTALL
if (-not $Force -and -not $PSCmdlet.ShouldContinue(#"
You are about to install support for direct execution of Unix-style scripts
that do not have a filename extension and instead define the interpreter to run
them with via shebangline ("#!/path/to/interpreter").
Support will be installed for $(("the CURRENT USER only`n(invoke as admin with -Scope AllUsers to change that)", 'ALL USERS')[$forAllUsers]).
Once installed, you will be able to run such scripts by direct invocation,
via a helper PowerShell script that analyzes the shebang line and calls the
appropriate interpreter.
CAVEATS:
* ENABLING THIS INVOCATION MECHANISM IS A SECURITY RISK, because any
plain-text file without an extension that has a shebang line
effectively becomes executable, potentially bypassing security features
that focus on files that have extensions known to be executable.
* AS OF POWERSHELL CORE 6.2.0, direct execution of such extension-less files
from PowerShell INVARIABLY RUNS IN A NEW CONSOLE WINDOW, WHICH MAKES USE
FROM POWERSHELL VIRTUALLY USELESS.
However, this is a BUG that should be fixed; see:
https://github.com/PowerShell/PowerShell/issues/7769
The following modifications to your system will be performed:
* "." will be added persistently to your `$env:PATHEXT variable, to enable
direct execution of filenames without extension.
NOTE: If you install with -Scope CurrentUser (the default), a static
user-level copy of the machine-level PATHEXT environment variable is
created, unless already present.
* The following registry locations will be created or replaced to define a
new file type for extension-less filenames:
$($regkeyExtensionToFileType -replace '^registry::')
$($regkeyFileType -replace '^registry::')
* The helper PowerShell script will be created in:
$helperScriptFullName
NOTE: Any existing registry definitions or helper script will be REPLACED.
Press ENTER to proceed, or CTRL-C to abort.
"#, "Shebang-Script Direct-Execution Support - Installation")) {
# !! The prompt defaults to *Yes* (!)
# !! Sadly, if we wanted the prompt to be default to *No*, we'de be forced
# !! to also present pointless 'Yes/No to *All*' prompts, which would be confusing.
# !! See https://github.com/PowerShell/PowerShell/issues/9428
exit 1
}
# Call the installation helper function
install
}
Write-Verbose "Shebang-support ${operation} completed."
if (-not $Force) {
Write-Host "Shebang-support ${operation} completed."
}
exit 0
[1] In Windows PowerShell, you can use Get-ChildItem -File -Filter *. to find extension-less files more conveniently and efficiently, but this feature is broken in PowerShell Core as of v6.2.0 - see this GitHub issue.

Include multiline python code into a Makefile

When I'm working on a bash script and need to write a particularly complex logic I usually fall back on using python, like this:
#!/bin/bash
function foo() {
python << END
if 1:
print "hello"
END
}
foo
How can I do the same thing from within a Makefile?
You may write a bash script containing your functions, say myscript.sh:
#!/bin/bash
foo() {
python << END
if 1:
print "hello $1"
END
}
Now here is a Makefile:
SHELL = /bin/bash
mytarget ::
#source myscript.sh ;\
foo world
Finally type in your terminal:
$ make mytarget
hello world
Some explanations on the Makefile: defining SHELL let make know which shell to run. The :: stands for phony target (and a little more); you can replace it with : for an actual target.
The key point is to run source and call the function in the same shell, that is, in the same line (since make run a different shell for each line); this is achieved by ;\ at the end of each line.

Python/Shell script String issues

I'm trying to customize my zsh prompt. The function below calls a Python script and returns the entire working directory path minus just the current directory. E.g. ~/research would go to ~. This is a .zsh-theme file.
function collapse_pwd {
echo $(python ~/.oh-my-zsh/themes/truncatecwd.py '%~' '%c')
}
This is the python script, truncatecwd.py.
#!/usr/bin/env python
import sys
cwd = sys.argv[1]
current_dir_end = sys.argv[2]
sys.stdout.write(cwd[0: cwd.index(current_dir_end)])
Weird things happen here. I keep getting errors saying that current_dir_end can't be found in cwd. I think that it has something to do with string formatting. I printed out cwd, and it seems to be correct: '~/.oh-my-zsh/themes'. However, when I call length on it, I get 2. Same goes for current_dir_end: I get length 2. In fact, even cwd = '~' returns a length of 2. Clearly, something subtle (but probably simple) is going on.
Thanks for your help.
I don't really understand what you're trying to do here, but wouldn't the following suffice, with no Python involved at all?
collapse_pwd() {
local result=${1:-$PWD}
if [[ $result = */* ]]; then
result="${result%/*}"
fi
if [[ $result = "$HOME"/* ]]; then
result="~/${result#$HOME/}"
fi
echo "$result"
}
could you do something like this:
import os
import sys
cwd = os.getcwd()
ret = os.path.sep.join(cwd.split(os.path.sep)[:-1])
sys.stdout.write(ret)
also, just an observation, because I'm not too familiar with zsh you may need to call python with -u option to ensure unbuffered output otherwise a newline may be written and that wouldn't be good with a command prompt.

How to write inline files in a Makefile

I've seen some shell scripts in which they pass a file by writing the contents of the file in the same shell script. For instance:
if [[ $uponly -eq 1 ]]; then
mysql $tabular -h database1 -u usertrack -pabulafia usertrack << END
select host from host_status where host like 'ld%' and status = 'up';
END
exit 0
fi
I've been able to do something similar in which I do:
python << END
print 'hello world'
END
If the name of the script is say myscript.sh then I can run it by executing sh myscript.sh. and I obtain my desired output.
Question is, can we do something similar in a Makefile? I've been looking around and they all say that I have do something like:
target:
python #<<
print 'hello world'
<<
But that doesn't work.
Here are the links where I've been looking:
http://www.opussoftware.com/tutorial/TutMakefile.htm#Response%20Files
http://www.scribd.com/doc/2369245/Makefile-Memo
You can do something like this:
define TMP_PYTHON_PROG
print 'hello'
print 'world'
endef
export TMP_PYTHON_PROG
target:
#python -c "$$TMP_PYTHON_PROG"
First, you're defining a multi line variable with define and endef. Then you need to export it to the shell otherwise it will treat each new line as a new command. Then you reinsert the shell variable using $$.
The reason your #<< thing didn't work is that it appears to be a feature of a non-standard make variant. Similarly, the define command that mVChr mentions is specific (as far as I'm aware) to GNU Make. While GNU Make is very widely distributed, this trick won't work in a BSD make, nor in a POSIX-only make.
I feel it's good, as a general principle, to keep makefiles as portable as possible; and if you're writing a Makefile in the context of an autoconf-ed system, it's more important still.
A fully portable technique for doing what you're looking for is:
target:
{ echo "print 'First line'"; echo "print 'second'"; } | python
or equivalently, if you want to lay things out a bit more tidily:
target:
{ echo "print 'First line'"; \
echo "print 'second'"; \
} | python

Using a python script as the filter for git filter-branch

I'm trying to rename some committers in a git repository, using git filter-branch. I'd quite like to use some more complex logic, but I don't really understand bash. The (working) script I'm currently using looks like this:
git filter-branch -f --tag-name-filter cat --env-filter '
cn="$GIT_COMMITTER_NAME"
cm="$GIT_COMMITTER_EMAIL"
if [ $cn = "ew" ]
then
cn="Eric"
cm="my.email#provider.com"
fi
export GIT_COMMITTER_NAME="$cn"
export GIT_COMMITTER_EMAIL="$cm"
' -- --all
Is it possible for me to use a python script as the --env-filter argument? If so, how would I get access to $GIT_COMMITTER_NAME to read and write it?
How would I do the equivalent of that bash string in a python file?
In python, you need to import os, after which os.environ is a dictionary with the incoming environment in it. Changes to os.environ are exported automatically. The real problem here is that git's --filter-* filters are run, as it says:
always evaluated in the shell context using the eval command (with the notable exception of the commit filter, for technical reasons).
So it's actually using the shell, and if you have the shell invoke Python, you wind up with a subprocess of the shell and any changes made in the Python process do not affect that shell. You'd have to eval the output of a Python script:
eval `python foo.py`
where foo.py outputs the appropriate export commands:
import os
def example():
cn = os.environ['GIT_COMMITTER_NAME']
cm = os.environ['GIT_COMMITTER_EMAIL']
if cn == 'ew':
cn = 'Eric'
cm = 'my.email#provider.com'
print ('export GIT_COMMITTER_NAME="%s"' % cn)
print ('export GIT_COMMITTER_EMAIL="%s"' % cm)
example() # or if __name__ == '__main__', etc.
(all of the above is untested).

Categories