Environment variables in symbolic links
Symbolic links are handled by the kernel, and the kernel does not care about environment variables. So, no.
unexpected behavior when creating a path with %userProfile% environment variable in pwershell
Shortcut files (
.lnk
) do supportcmd.exe
-style environment variable references (e.g.%USERPROFILE%
) in their properties, but there's a caveat:When a shortcut file is created or modified with WshShell COM object's
.CreateShortcut()
API:Assigning property values, say,
.TargetPath = '%USERPROFILE%\Desktop'
works as expected - that is, the string is stored as-is and the reference to environment variable%USERPROFILE%
is only expanded at runtime, i.e. when the shortcut file is opened.However, querying such properties via this COM API also performs expansion, so that you won't be able to get raw definitions, and won't be able to distinguish between the
.TargetPath
property containing%USERPROFILE%\Desktop
and, say, verbatimC:\Users\jdoe\Desktop
When in doubt about a given
.lnk
file's actual property values, inspect it via File Explorer.
Symbolic links (symlinks) do not.
The target of a symbolic link must be specified as a literal path, which in your case means using PowerShell's environment-variable syntax (e.g., $env:UserProfile
) in order to resolve the variable reference to its value up front:
# This results in a *literal* path, because $env:UserProfile is
# instantly resolved; the result can be used with New-Item / mklink
$literalTargetPath = "$env:UserProfile\" + $midpart + $filename
Passing something like '%USERPROFILE%\Desktop'
to the -Value
/ -Target
parameter of New-Item -Type SymbolicLink
therefore does not work (as intended), but the symptom depends on which PowerShell edition you're running:
Windows PowerShell, which you're using, tries to verify the existence of the target path up front, and therefore fails to create the symlink, because it interprets
%USERPROFILE%\Desktop
verbatim, as a relative path (relative to the current directory, which is why the latter's path is prepended), which doesn't exist.PowerShell (Core) 7+ also interprets the path verbatim, but it allows creating symlinks with not-yet-existing targets, so the symlink creation itself succeeds. However, trying to use the resulting symlink then fails, because its target doesn't exist.
Symbolic Links to Variables
Aside from the scoping issues in your code, there is really one issue to keep in mind. Python does everything by binding a name to a reference. You are interested in getting the value assigned to a name, not to the reference it may have been bound to at registration time. This is sort of like a pointer-to-a-pointer in C and C++.
To keep track of arbitrary names, you need to keep track of their enclosing namespace as well. That way you can use the universal function getattr
to fetch the latest value of your name. A namespace can be any instance (modules and classes are instances too).
Your registration method needs to keep track of the object containing the name of interest, the name itself, and the key you want to display it as:
op.register_output_variable(self, 'foo', "Foo")
The manager class can store the values in a dictionary. Instead of a nested dictionary, I'd recommend using a tuple
or collections.namedtuple
to hold the "pointer":
def register_output_variable(self, inst, attr, key):
self.output_vars_data[key] = (inst, attr)
When you output the variables, do something like
for key, spec in self.output_vars_data.items():
print(key, '=', getattr(*spec))
What this does is provide you with a way to reference object attributes by name instead of reference. If you set x.a = 1
, register x.a
, then do x.a += 1
, you'd want to get 2
as your value. The important thing to remember is that x.a
will be bound to a different reference because integers are immutable in Python. You completely bypass this by registering x
as the namespace, and 'a'
as the name.
You can generalize further by adding a method to register indexable elements, like the elements of a list and values of a dictionary. You would register the instance and index/key instead of instance and attribute name. Take a look at operator.attrgetter
and operator.itemgetter
for more ideas.
This is just a broad suggestion. It does not address the inconsistent mess of static, class and instance namespaces you are using.
What is the proper way to make symbolic links with regard to file paths being full or shortened and relying on token expansion
Consider:
ln -s target linkname
The complication occurs only when target
is specified by a relative path. If it is, then the path is relative to the directory that holds the file linkname
. The currect working directory is always ignored.
As an example, consider
ln -s hist ~/Desktop/hist
This command creates a link named hist
in ~/Desktop
. Since no path is given for the target, the target is interpreted as also being in the directory ~/Desktop
. In this case, that means the the link hist
points to itself.
As another example, consider
cd /var/tmp
ln -s ../hist ~/Desktop/hist
This will create a link from ~/Desktop/hist
to ~/hist
because the ../
is interpreted relative to the directory that holds the link, ~/Desktop
. The directory that we are in when the ln
command is executed and the directory that we are in when we access /Desktop/hist
are both irrelevant.
Another subtlety
ln
does not care what you use for target
when the command is issued: it can be arbitrary text. The value of target
is not intepreted until an attempt is made to access linkname
. Consider:
$ ln -s "mary had a little lamb" ~/file1
$ ls -alt ~/file1
lrwxrwxrwx 1 user group 22 Sep 15 21:47 /home/user/file1 -> mary had a little lamb
$ cat ~/file1
cat: /home/user/file1: No such file or directory
If we want we can later create that file:
$ echo this is a test >~/"mary had a little lamb"
$ cat ~/file1
this is a test
Again, the existence of the target is only checked when an attempt is made to access the link.
Documentation
This behavior is documented in man ln
:
Symbolic links can hold arbitrary text; if later resolved, a relative
link is interpreted in relation to its parent directory.
How to know when you are in a symbolic link
If you don't find anything else, you can use
os.getenv("PWD")
It's not really a portable python method, but works on POSIX systems. It gets the value of the PWD
environment variable, which is set by the cd
command (if you don't use cd -P
) to the path name you navigated into (see man cd
) before running the python script. That variable is not altered by python, of course. So if you os.chdir
somewhere else, that variable will retain its value.
Anyway, as a side node, /tmp/foo/kiwi
is the directory you are in. I'm not sure whether anything apart from the shell knows that you've really navigated through another path into that place, actually :)
Creating symlinks with ansible using variables
The issue is that you're passing two different objects to the with_items
property. The first object has two properties (src_dir
and src_name
), while the second object has two different properties (dest_dir
and dest_name
).
It looks like you want to combine them into a single object like this:
- name: Create NPM symlink
file:
src: '{{ item.src_dir }}/{{ item.src_name }}'
dest: '{{ item.dest_dir }}/{{ item.dest_name }}'
owner: "{{ ansible_ssh_user }}"
group: "{{ ansible_ssh_user }}"
state: link
with_items:
- { src_dir: "{{ npm_real_dir }}", src_name: "{{ npm_real_name }}", dest_dir: "{{ nodenpm_link_dir }}", dest_name: "{{ npm_link_name }}" }
That should work better and get rid of the error, but in this case you don't really need the with_items
, since it's only one item that you're dealing with. You can add more objects for other tools, e.g. gulp
in the same manner, e.g. like this:
- name: Create symlinks
file:
src: '{{ item.src_dir }}/{{ item.src_name }}'
dest: '{{ item.dest_dir }}/{{ item.dest_name }}'
owner: "{{ ansible_ssh_user }}"
group: "{{ ansible_ssh_user }}"
state: link
with_items:
- { src_dir: "{{ npm_real_dir }}", src_name: "{{ npm_real_name }}", dest_dir: "{{ nodenpm_link_dir }}", dest_name: "{{ npm_link_name }}" }
- { src_dir: "{{ gulp_real_dir }}", src_name: "{{ gulp_real_name }}", dest_dir: "{{ gulp_link_dir }}", dest_name: "{{ gulp_link_name }}" }
Automate creation of symbolic links on Windows bash
Use the following:
powershell 'Start-Process -Verb RunAs cmd /k, " cd `"$PWD`" & mklink /D `"link_to_utils`" `"common\utils`" "'
Since it is PowerShell that interprets that verbatim content of the command line being passed, its syntax rules must be followed, meaning that a
"..."
(double-quoted) string is required for expansion (string interpolation) of the automatic$PWD
variable to occur, and that embedded"
characters inside that string must be escaped as`"
(""
would work too).The pass-through command line for
cmd.exe
is passed as a single string argument, for conceptual clarity.
Related Topics
How to Invoke Any Kernel Function
How to Get The Output of at Command in Current or Another Terminal Window
Analyze Memory with Crash with Kdump
Linux Configuration - Ssmtp: Cannot Open Smtp.Gmail.Com:587
Raw Socket Access as Normal User on Linux 2.4
Git Clone Gnutls Recv Error (-9): a Tls Packet with Unexpected Length Was Received
Detect When Reader Closes Named Pipe (Fifo)
Does Gcc Support Command Files
Google API to Find The Search Count
How to Handle Error/Exception in Shell Script
Profiling Arbitrary Cuda Applications
How to Use Opengl Without a Window Manager in Linux
Detect If Interface Is in Promiscuous Mode with C
Nasm X86_64 Assembly in 32-Bit Mode: Why Does This Instruction Produce Rip-Relative Addressing Code
How to Rename a Shared Library to Avoid Same-Name Conflict
How to Change File Extension in Linux Shell Script
How to Highlight The Differences Between Subsequent Lines in a File
Set Cron Job for 1St Working Day of Every Month in Shell Scripting