Getting a symbol from a string without it being replaced by its value?

Consider the following code running on a fresh Mathematica session with no symbols defined:

p[s_String] := Protect[s];
p["x"]


This doesn't do anything because there is no symbol named x yet, so no symbols match the string pattern "x". No problem, we can resolve this by using Symbol:

p[s_String] := Protect[Symbol[s]];
p["x"]


This doesn't work either, since Protect has the attribute HoldAll. So we can use Evaluate:

p[s_String] := Protect[Evaluate[Symbol[s]]];
p["x"]


Great! Now it works. Except... If the symbol we want to protect does already exist, then it won't work:

y = 1;
p[s_String] := Protect[Evaluate[Symbol[s]]];
p["y"]


This will execute Protect[1] because Symbol["y"] evaluates to 1. How can one write a function p which protects a symbol, given via a string, whether or not it already exists?

Thanks.

EDIT: A solution was given in the comments:

p[s_String] := Protect @@ ToExpression[s, StandardForm, Hold];


However, now I have another similar issue. I would like to find out if s is a proper symbol, so that e.g. p["1"] or p["a b"] will return an error message. So I tried the same trick with Head:

p[s_String] :=
If[Head @@ ToExpression[s, StandardForm, Hold] === Symbol,
Protect @@ ToExpression[s, StandardForm, Hold],
Print[s, " is not a symbol!"]];


This works only if the symbol was not defined previously. For example, if I type x = 1 and then p["x"] I will get x is not a symbol because its Head is Integer. So again, it's the same problem as above: How can I look at the symbol itself without it being replaced by its value?

• What if you just did p[s_String] := If[MemberQ[Names[s], s], Protect[s]] Aug 6, 2021 at 15:17
• Protect @@ ToExpression[s, StandardForm, Hold]? Aug 6, 2021 at 16:23
• @flinty: But then it won't be protected if it doesn't already exist. I want to protect that symbol from future modifications whether or not it already exists.
– CMB
Aug 6, 2021 at 17:38
• @MichaelE2: Thanks, I think that works!
– CMB
Aug 6, 2021 at 17:40
• I now have a similar problem, for which this trick doesn't work. Please see my edit. Thanks!
– CMB
Aug 6, 2021 at 18:36

p[s_String] :=

This solution simply lets Protect "do the work for me" (as suggested by @MichaelE2 in the comments). In my actual code, there is much more that happens after calling Protect, but I only continue the evaluation if Protect was successful.